mirror of
https://gitee.com/dromara/hutool.git
synced 2025-05-03 20:27:58 +08:00
[feature] 添加解析车辆识别代码
This commit is contained in:
parent
a55aed6a5a
commit
9c0a343491
@ -0,0 +1,22 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle;
|
||||||
|
|
||||||
|
import org.dromara.hutool.extra.vehicle.vin.Vin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汽车工具类封装
|
||||||
|
*
|
||||||
|
* @author VampireAchao
|
||||||
|
*/
|
||||||
|
public class VehicleUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析车辆识别代码
|
||||||
|
*
|
||||||
|
* @param vin 车辆识别代码
|
||||||
|
* @return 解析后的结果
|
||||||
|
*/
|
||||||
|
public static Vin parseVin(String vin) {
|
||||||
|
return Vin.of(vin);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.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 索引位
|
||||||
|
* @throws IllegalArgumentException the illegal argument exception
|
||||||
|
*/
|
||||||
|
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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.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();
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,211 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.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 + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.vin;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汽车Vin字码抽象.
|
||||||
|
*
|
||||||
|
* @author dax
|
||||||
|
* @since 2023 /5/14 17:42
|
||||||
|
*/
|
||||||
|
interface VinCode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字码值
|
||||||
|
*
|
||||||
|
* @return the code
|
||||||
|
*/
|
||||||
|
String getCode();
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle.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$");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package org.dromara.hutool.extra.vehicle;
|
||||||
|
|
||||||
|
import org.dromara.hutool.extra.vehicle.vin.Vin;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Year;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author VampireAchao
|
||||||
|
* @since 2023/5/31 14:43
|
||||||
|
*/
|
||||||
|
public class VehicleTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseVinTest() {
|
||||||
|
String vinStr = "HE9XR1C48PS083871";
|
||||||
|
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());
|
||||||
|
// VDS
|
||||||
|
Assertions.assertEquals("XR1C4", vin.vdsCode());
|
||||||
|
// 车型年份
|
||||||
|
Assertions.assertEquals(Year.of(2023), vin.defaultYear());
|
||||||
|
// OEM厂商
|
||||||
|
Assertions.assertEquals("S", vin.oemCode());
|
||||||
|
// 生产序号
|
||||||
|
Assertions.assertEquals("871", vin.prodNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user