From 86710d6c2090aafebf0d27541becc108fb96a362 Mon Sep 17 00:00:00 2001 From: duandazhi Date: Sat, 20 Mar 2021 23:29:27 +0800 Subject: [PATCH 1/3] =?UTF-8?q?1=E3=80=81=E5=A2=9E=E5=8A=A0=E8=84=B1?= =?UTF-8?q?=E6=95=8F=E4=B8=93=E4=B8=9A=E5=B7=A5=E5=85=B7=E7=B1=BB=20Desens?= =?UTF-8?q?itizedUtils=20=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=AF=B9=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7id=E3=80=81=E4=B8=AD=E6=96=87=E5=90=8D=E3=80=81?= =?UTF-8?q?=E8=BA=AB=E4=BB=BD=E8=AF=81=E5=8F=B7=E3=80=81=E5=BA=A7=E6=9C=BA?= =?UTF-8?q?=E5=8F=B7=E3=80=81=E6=89=8B=E6=9C=BA=E5=8F=B7=E3=80=81=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E3=80=81=E7=94=B5=E5=AD=90=E9=82=AE=E4=BB=B6=E3=80=81?= =?UTF-8?q?=E5=AF=86=E7=A0=81=EF=BC=9B=20=20=20=20(=E7=94=9F=E4=BA=A7?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E4=B8=AD=E4=BD=BF=E7=94=A8=E7=9A=84=E5=BE=88?= =?UTF-8?q?=E5=A4=9A=EF=BC=8C=E5=B8=8C=E6=9C=9B=E9=87=87=E7=BA=B3=EF=BC=8C?= =?UTF-8?q?=E8=AF=A5=E8=84=B1=E6=95=8F=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E6=9E=81=E5=A4=A7=E7=AE=80=E5=8C=96=20?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E4=B8=AD=E8=84=B1=E6=95=8F=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E7=9A=84=E9=9C=80=E6=B1=82)=202=E3=80=81CharSequenceUtil?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C=E5=A2=9E=E5=8A=A0=EF=BC=9A?= =?UTF-8?q?Left/Right/Mid=20=E7=94=A8=E6=9D=A5=E5=85=BC=E5=AE=B9=20org.apa?= =?UTF-8?q?che.commons.lang3.StringUtils=20=E4=BD=BF=E7=94=A8=E4=B9=A0?= =?UTF-8?q?=E6=83=AF;=20=20=20=20=E7=9B=AE=E5=89=8DCharSequenceUtil?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=E4=B8=AD=E6=9C=89=20=E7=B1=BB?= =?UTF-8?q?=E4=BC=BC=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BD=86=E6=98=AF?= =?UTF-8?q?=E5=92=8C=20Left/Right/Mid=20=E5=8A=9F=E8=83=BD=E8=BF=98?= =?UTF-8?q?=E6=98=AF=E6=9C=89=E5=BE=88=E5=A4=A7=E5=B7=AE=E8=B7=9D=E3=80=82?= =?UTF-8?q?=203=E3=80=81=E6=96=B0=E5=A2=9E=E7=9A=84DesensitizedUtils=20?= =?UTF-8?q?=E5=92=8C=20=20CharSequenceUtil=E6=96=B0=E5=A2=9Eleft=E3=80=81r?= =?UTF-8?q?ight=E3=80=81mid=E6=96=B9=E6=B3=95=E9=83=BD=E7=BB=8F=E8=BF=87?= =?UTF-8?q?=E4=BA=86=E6=B5=8B=E8=AF=95=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/text/CharSequenceUtil.java | 460 +++++++++++++++++- .../hutool/core/util/DesensitizedUtils.java | 154 ++++++ .../core/util/DesensitizedUtilsTest.java | 58 +++ .../java/cn/hutool/core/util/StrUtilTest.java | 110 +++++ 4 files changed, 776 insertions(+), 6 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/util/DesensitizedUtils.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/util/DesensitizedUtilsTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index a594d9e81..3330a06aa 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -5,12 +5,7 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.func.Func1; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.ReUtil; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.*; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -47,6 +42,12 @@ public class CharSequenceUtil { */ public static final String SPACE = " "; + /** + *

The maximum size to which the padding constant(s) can expand.

+ *

填充常量可以最大填充的数量

+ */ + private static final int PAD_LIMIT = 8192; + /** *

字符串是否为空白,空白的定义如下:

*
    @@ -2370,6 +2371,12 @@ public class CharSequenceUtil { /** * 重复某个字符 * + *
    +	 * StrUtil.repeat('e', 0)  = ""
    +	 * StrUtil.repeat('e', 3)  = "eee"
    +	 * StrUtil.repeat('e', -2) = ""
    +	 * 
    + * * @param c 被重复的字符 * @param count 重复的数目,如果小于等于0则返回"" * @return 重复字符字符串 @@ -2960,11 +2967,13 @@ public class CharSequenceUtil { /** * 补充字符串以满足指定长度,如果提供的字符串大于指定长度,截断之 + * 同:leftPad (org.apache.commons.lang3.leftPad) * *
     	 * StrUtil.padPre(null, *, *);//null
     	 * StrUtil.padPre("1", 3, "ABC");//"AB1"
     	 * StrUtil.padPre("123", 2, "ABC");//"12"
    +	 * StrUtil.padPre("1039", -1, "0");//"103"
     	 * 
    * * @param str 字符串 @@ -2989,6 +2998,7 @@ public class CharSequenceUtil { /** * 补充字符串以满足最小长度,如果提供的字符串大于指定长度,截断之 + * 同:leftPad (org.apache.commons.lang3.leftPad) * *
     	 * StrUtil.padPre(null, *, *);//null
    @@ -3023,6 +3033,7 @@ public class CharSequenceUtil {
     	 * StrUtil.padAfter(null, *, *);//null
     	 * StrUtil.padAfter("1", 3, '0');//"100"
     	 * StrUtil.padAfter("123", 2, '0');//"23"
    +	 * StrUtil.padAfter("123", -1, '0')//"" 空串
     	 * 
    * * @param str 字符串,如果为{@code null},直接返回null @@ -3596,6 +3607,17 @@ public class CharSequenceUtil { /** * 替换指定字符串的指定区间内字符为"*" + * 俗称:脱敏功能,后面其他功能,可以见:DesensitizedUtils(脱敏工具类) + * + *
    +	 * StrUtil.hide(null,*,*)=null
    +	 * StrUtil.hide("",0,*)=""
    +	 * StrUtil.hide("jackduan@163.com",-1,4)   ****duan@163.com
    +	 * StrUtil.hide("jackduan@163.com",2,3)    ja*kduan@163.com
    +	 * StrUtil.hide("jackduan@163.com",3,2)    jackduan@163.com
    +	 * StrUtil.hide("jackduan@163.com",16,16)  jackduan@163.com
    +	 * StrUtil.hide("jackduan@163.com",16,17)  jackduan@163.com
    +	 * 
    * * @param str 字符串 * @param startInclude 开始位置(包含) @@ -3607,6 +3629,84 @@ public class CharSequenceUtil { return replace(str, startInclude, endExclude, '*'); } + /** + * 脱敏,使用默认的脱敏策略 + *
    +	 * StrUtil.hide("100", DesensitizedUtils.DesensitizedType.USER_ID)) =  "0"
    +	 * StrUtil.hide("段正淳", DesensitizedUtils.DesensitizedType.CHINESE_NAME)) = "段**"
    +	 * StrUtil.hide("51343620000320711X", DesensitizedUtils.DesensitizedType.ID_CARD)) = "5***************1X"
    +	 * StrUtil.hide("09157518479", DesensitizedUtils.DesensitizedType.FIXED_PHONE)) = "0915*****79"
    +	 * StrUtil.hide("18049531999", DesensitizedUtils.DesensitizedType.MOBILE_PHONE)) = "180****1999"
    +	 * StrUtil.hide("北京市海淀区马连洼街道289号", DesensitizedUtils.DesensitizedType.ADDRESS)) = "北京市海淀区马********"
    +	 * StrUtil.hide("duandazhi-jack@gmail.com.cn", DesensitizedUtils.DesensitizedType.EMAIL)) = "d*************@gmail.com.cn"
    +	 * StrUtil.hide("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)) = "**********"
    +	 * 
    + * + * @see DesensitizedUtils 如果需要自定义,脱敏规则,请使用该工具类; + * @param str 字符串 + * @param desensitizedType 脱敏类型;可以脱敏:用户id、中文名、身份证号、座机号、手机号、地址、电子邮件、密码 + * @return 脱敏之后的字符串 + * @since 5.6.2 + */ + public static String hide(CharSequence str, DesensitizedUtils.DesensitizedType desensitizedType) { + if (isBlank(str)) { + return EMPTY; + } + String newStr = String.valueOf(str); + switch (desensitizedType) { + case USER_ID: + newStr = String.valueOf(DesensitizedUtils.userId()); + break; + case CHINESE_NAME: + newStr = DesensitizedUtils.chineseName(String.valueOf(str)); + break; + case ID_CARD: + newStr = DesensitizedUtils.idCardNum(String.valueOf(str),1,2); + break; + case FIXED_PHONE: + newStr = DesensitizedUtils.fixedPhone(String.valueOf(str)); + break; + case MOBILE_PHONE: + newStr = DesensitizedUtils.mobilePhone(String.valueOf(str)); + break; + case ADDRESS: + newStr = DesensitizedUtils.address(String.valueOf(str), 8); + break; + case EMAIL: + newStr = DesensitizedUtils.email(String.valueOf(str)); + break; + case PASSWORD: + newStr = DesensitizedUtils.password(String.valueOf(str)); + break; + default: + } + return newStr; + } + + /** + * 脱敏,使用默认的脱敏策略 + * + *
    +	 * StrUtil.desensitized("100", DesensitizedUtils.DesensitizedType.USER_ID)) =  "0"
    +	 * StrUtil.desensitized("段正淳", DesensitizedUtils.DesensitizedType.CHINESE_NAME)) = "段**"
    +	 * StrUtil.desensitized("51343620000320711X", DesensitizedUtils.DesensitizedType.ID_CARD)) = "5***************1X"
    +	 * StrUtil.desensitized("09157518479", DesensitizedUtils.DesensitizedType.FIXED_PHONE)) = "0915*****79"
    +	 * StrUtil.desensitized("18049531999", DesensitizedUtils.DesensitizedType.MOBILE_PHONE)) = "180****1999"
    +	 * StrUtil.desensitized("北京市海淀区马连洼街道289号", DesensitizedUtils.DesensitizedType.ADDRESS)) = "北京市海淀区马********"
    +	 * StrUtil.desensitized("duandazhi-jack@gmail.com.cn", DesensitizedUtils.DesensitizedType.EMAIL)) = "d*************@gmail.com.cn"
    +	 * StrUtil.desensitized("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)) = "**********"
    +	 * 
    + * + * @see DesensitizedUtils 如果需要自定义,脱敏规则,请使用该工具类; + * @param str 字符串 + * @param desensitizedType 脱敏类型;可以脱敏:用户id、中文名、身份证号、座机号、手机号、地址、电子邮件、密码 + * @return 脱敏之后的字符串 + * @since 5.6.2 + */ + public static String desensitized(CharSequence str, DesensitizedUtils.DesensitizedType desensitizedType) { + return hide(str, desensitizedType); + } + /** * 替换字符字符数组中所有的字符为replacedStr
    * 提供的chars为所有需要被替换的字符,例如:"\r\n",则"\r"和"\n"都会被替换,哪怕他们单独存在 @@ -4254,4 +4354,352 @@ public class CharSequenceUtil { } return strBuilder.toString(); } + + // Left/Right/Mid 用来兼容 org.apache.commons.lang3.StringUtils 使用习惯 start + //------------------------------------------------------------------------- + /** + *

    获取字符串的最左{@code len}个字符。

    + * + *

    如果{@code len}个字符不可用,或者字符串为{@code null}, + * 则将无例外地返回该字符串。 + * 如果len为负,则返回一个空字符串。 + *

    + * + *

    +	 * StrUtil.left(null, *)    = null
    +	 * StrUtil.left(*, -ve)     = ""
    +	 * StrUtil.left("", *)      = ""
    +	 * StrUtil.left("neu", 0)   = ""
    +	 * StrUtil.left("neu", 2)   = "ne"
    +	 * StrUtil.left("neu", 4)   = "neu"
    +	 * 
    + * + * @param str 从中获取最左边字符的字符串,可以为null + * @param len the length of the required String + * @return the left most characters, {@code null} if null String input + */ + public static String left(final String str, final int len) { + if (str == null) { + return null; + } + if (len < 0) { + return EMPTY; + } + if (str.length() <= len) { + return str; + } + return str.substring(0, len); + } + + /** + *

    用空格在左侧填充字符串 (' ').

    + * + *

    填充字符串到指定的长度 {@code size}.

    + * + *
    +	 * StrUtil.leftPad(null, *)   = null
    +	 * StrUtil.leftPad("", 3)     = "   "
    +	 * StrUtil.leftPad("bat", 1)  = "bat"
    +	 * StrUtil.leftPad("bat", -1) = "bat"
    +	 * StrUtil.leftPad("bat", 3)  = "bat"
    +	 * StrUtil.leftPad("bat", 5)  = "  bat"
    +	 *
    +	 * 
    + * + * @param str 要填充的字符串,可以为null + * @param size 填充到size长度 + * @return 如果不需要填充,请使用左填充字符串或原始字符串, + * {@code null} if null String input + */ + public static String leftPad(final String str, final int size) { + return leftPad(str, size, ' '); + } + + /** + *

    Left pad a String with a specified character.

    + *

    左侧用指定的字符串进行填充.

    + * + *

    Pad to a size of {@code size}.

    + *

    填充到指定的长度 {@code size}.

    + * + *
    +	 * StrUtil.leftPad(null, *, *)     = null
    +	 * StrUtil.leftPad("", 3, 'z')     = "zzz"
    +	 * StrUtil.leftPad("bat", -1, 'z') = "bat"
    +	 * StrUtil.leftPad("bat", 1, 'z')  = "bat"
    +	 * StrUtil.leftPad("bat", 3, 'z')  = "bat"
    +	 * StrUtil.leftPad("bat", 5, 'z')  = "zzbat"
    +	 *
    +	 * 
    + * + * @param str the String to pad out, may be null; 要填充的字符串,可以为null + * @param size the size to pad to; 填充到size长度 + * @param padChar the character to pad with; 用来填充的字符 + * @return left padded String or original String if no padding is necessary, + * 左侧填充字符串 or 如果不需要填充则返回原始字符串; + * {@code null} if null String input; 如果输入字符串是null + * @since 2.0 + */ + public static String leftPad(final String str, final int size, final char padChar) { + if (str == null) { + return null; + } + final int pads = size - str.length(); + if (pads <= 0) { + return str; // 尽可能返回原始字符串 returns original String when possible + } + if (pads > PAD_LIMIT) { + return leftPad(str, size, String.valueOf(padChar)); + } + return repeat(padChar, pads).concat(str); + } + + /** + *

    用指定的字符串左填充字符串.

    + * + *

    填充到指定的长度 {@code size}.

    + * + *
    +	 * StrUtil.leftPad(null, *, *)      = null
    +	 * StrUtil.leftPad("", 3, "z")      = "zzz"
    +	 * StrUtil.leftPad("bat", 3, "yz")  = "bat"
    +	 * StrUtil.leftPad("bat", 5, "yz")  = "yzbat"
    +	 * StrUtil.leftPad("bat", 8, "yz")  = "yzyzybat"
    +	 * StrUtil.leftPad("bat", 1, "yz")  = "bat"
    +	 * StrUtil.leftPad("bat", -1, "yz") = "bat"
    +	 * StrUtil.leftPad("bat", 5, null)  = "  bat"
    +	 * StrUtil.leftPad("bat", 5, "")    = "  bat"
    +	 * 
    + * + * @param str 要填充的字符串,可以为null + * @param size 填充到指定的长度 + * @param padStr 要填充的字符串,如果为null或空,则将其视为单个空格 + * @return 如果不需要填充,请使用左填充字符串或原始字符串, + * {@code null} 如果为null字符串输入 + */ + public static String leftPad(final String str, final int size, String padStr) { + if (str == null) { + return null; + } + if (isEmpty(padStr)) { + padStr = SPACE; + } + final int padLen = padStr.length(); + final int strLen = str.length(); + final int pads = size - strLen; + if (pads <= 0) { + return str; // 当有可能返回原始字符串; returns original String when possible + } + if (padLen == 1 && pads <= PAD_LIMIT) { + return leftPad(str, size, padStr.charAt(0)); + } + + if (pads == padLen) { + return padStr.concat(str); + } else if (pads < padLen) { + return padStr.substring(0, pads).concat(str); + } else { + final char[] padding = new char[pads]; + final char[] padChars = padStr.toCharArray(); + for (int i = 0; i < pads; i++) { + padding[i] = padChars[i % padLen]; + } + return new String(padding).concat(str); + } + } + + /** + *

    获取字符串的最右边{@code len}个字符。

    + * + *

    如果{@code len}个字符不可用,或者字符串为{@code null},则将无例外地返回该字符串。如果len为负,则返回一个空字符串。

    + * + *

    +	 * StrUtil.right(null, *)    = null
    +	 * StrUtil.right(*, -ve)     = ""
    +	 * StrUtil.right("", *)      = ""
    +	 * StrUtil.right("abc", -1)   = ""
    +	 * StrUtil.right("abc", 0)   = ""
    +	 * StrUtil.right("abc", 2)   = "bc"
    +	 * StrUtil.right("abc", 4)   = "abc"
    +	 * 
    + * + * @param str 从中获取最右边字符的字符串,可以为null + * @param len 所需字符串的长度 + * @return 最右边的字符,{@code null},如果为null字符串输入 + */ + public static String right(final String str, final int len) { + if (str == null) { + return null; + } + if (len < 0) { + return EMPTY; + } + if (str.length() <= len) { + return str; + } + return str.substring(str.length() - len); + } + + /** + *

    用空格('')右填充字符串。

    + *

    将字符串填充为{@code size}的大小。

    + * + *

    +	 * StrUtil.rightPad(null, *)   = null
    +	 * StrUtil.rightPad("", 3)     = "   "
    +	 * StrUtil.rightPad("bat", 3)  = "bat"
    +	 * StrUtil.rightPad("bat", 5)  = "bat  "
    +	 * StrUtil.rightPad("bat", 1)  = "bat"
    +	 * StrUtil.rightPad("bat", -1) = "bat"
    +	 * 
    + * + * @param str 要填充的字符串,可以为null + * @param size 填充的长度 + * @return 如果不需要填充,则使用右填充的字符串或原始字符串, + * {@code null} 如果为null字符串输入 + */ + public static String rightPad(final String str, final int size) { + return rightPad(str, size, ' '); + } + + /** + *

    用指定的字符填充到原字符串的右侧.

    + * + *

    填充字符串到几位 {@code size}.

    + * + *
    +	 * StrUtil.rightPad(null, *, *)     = null
    +	 * StrUtil.rightPad("", 3, 'z')     = "zzz"
    +	 * StrUtil.rightPad("bat", 3, 'z')  = "bat"
    +	 * StrUtil.rightPad("bat", 5, 'z')  = "batzz"
    +	 * StrUtil.rightPad("bat", 1, 'z')  = "bat"
    +	 * StrUtil.rightPad("bat", -1, 'z') = "bat"
    +	 * 
    + * + * @param str 要填充的字符串,可以为null + * @param size 要填充的到size位 + * @param padChar 用来填充的字符串 + * @return 返回 右侧填充字符串 or 如果不需要填充,则为原始String。 + * {@code null} 如果为null字符串输入 + * @since 2.0 + */ + public static String rightPad(final String str, final int size, final char padChar) { + if (str == null) { + return null; + } + final int pads = size - str.length(); + if (pads <= 0) { + return str; // 当有可能返回原始字符串; returns original String when possible + } + if (pads > PAD_LIMIT) { + return rightPad(str, size, String.valueOf(padChar)); + } + return str.concat(repeat(padChar, pads)); + } + + /** + *

    Right pad a String with a specified String.

    + *

    用自定的字符串进行右侧填充.

    + * + *

    The String is padded to the size of {@code size}.

    + *

    填充字符串到指定长度{@code size}.

    + * + *
    +	 * StrUtil.rightPad(null, *, *)      = null
    +	 * StrUtil.rightPad("", 3, "z")      = "zzz"
    +	 * StrUtil.rightPad("bat", 3, "yz")  = "bat"
    +	 * StrUtil.rightPad("bat", 5, "yz")  = "batyz"
    +	 * StrUtil.rightPad("bat", 8, "yz")  = "batyzyzy"
    +	 * StrUtil.rightPad("bat", 1, "yz")  = "bat"
    +	 * StrUtil.rightPad("bat", -1, "yz") = "bat"
    +	 * StrUtil.rightPad("bat", 5, null)  = "bat  "
    +	 * StrUtil.rightPad("bat", 5, "")    = "bat  "
    +	 * 
    + * + * @param str 要填充的字符串,可以为null + * @param size 填充到size长度 + * @param padStr 用来填充的字符串 + * @return right padded String or original String if no padding is necessary, + * 右侧填充字符串 or 如果不需要填充则返回原始字符串 + * {@code null} if null String input + */ + public static String rightPad(final String str, final int size, String padStr) { + if (str == null) { + return null; + } + if (isEmpty(padStr)) { + padStr = SPACE; + } + final int padLen = padStr.length(); + final int strLen = str.length(); + final int pads = size - strLen; + if (pads <= 0) { + return str; // 当有可能返回原始字符串; returns original String when possible + } + if (padLen == 1 && pads <= PAD_LIMIT) { + return rightPad(str, size, padStr.charAt(0)); + } + + if (pads == padLen) { + return str.concat(padStr); + } else if (pads < padLen) { + return str.concat(padStr.substring(0, pads)); + } else { + final char[] padding = new char[pads]; + final char[] padChars = padStr.toCharArray(); + for (int i = 0; i < pads; i++) { + padding[i] = padChars[i % padLen]; + } + return str.concat(new String(padding)); + } + } + + /** + *

    Gets {@code len} characters from the middle of a String.

    + *

    从字符串中间获取{@code len}个字符串.

    + * + *

    If {@code len} characters are not available, the remainder + * of the String will be returned without an exception. If the + * String is {@code null}, {@code null} will be returned. + * An empty String is returned if len is negative or exceeds the + * length of {@code str}.

    + * + *

    如果{@code len}个字符不可用,则将字符串的其余部分无例外地返回。 + * 如果字符串为{@code null},则将返回{@code null}。 + * 如果len为负或超过{@code str}的长度,则返回一个空字符串。

    + * + *

    +	 * StrUtil.mid(null, *, *)    = null
    +	 * StrUtil.mid(*, *, -ve)     = ""
    +	 * StrUtil.mid("", 0, *)      = ""
    +	 * StrUtil.mid("abc", 0, 2)   = "ab"
    +	 * StrUtil.mid("abc", 0, 4)   = "abc"
    +	 * StrUtil.mid("abc", 2, 4)   = "c"
    +	 * StrUtil.mid("abc", 4, 2)   = ""
    +	 * StrUtil.mid("abc", -2, 2)  = "ab"
    +	 * 
    + * + * + * @param str 给定字符串,可以为null; the String to get the characters from, may be null + * @param pos 其实位置,如果是负数则当做0; the position to start from, negative treated as zero + * @param len 要求字符串的长度; the length of the required String + * @return the middle characters, {@code null} if null String input + */ + public static String mid(final String str, int pos, final int len) { + if (str == null) { + return null; + } + if (len < 0 || pos > str.length()) { + return EMPTY; + } + if (pos < 0) { + pos = 0; + } + if (str.length() <= pos + len) { + return str.substring(pos); + } + return str.substring(pos, pos + len); + } + // Left/Right/Mid 用来兼容 org.apache.commons.lang3.StringUtils 使用习惯 end + //------------------------------------------------------------------------- } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/DesensitizedUtils.java b/hutool-core/src/main/java/cn/hutool/core/util/DesensitizedUtils.java new file mode 100644 index 000000000..a6f64cf31 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/util/DesensitizedUtils.java @@ -0,0 +1,154 @@ +package cn.hutool.core.util; + +/** + * 脱敏utils + * @author dazer and neusoft and qiaomu + * @date 2021/3/20 09:55 + * @since 5.6.2 + */ +public class DesensitizedUtils { + public enum DesensitizedType { + //用户id + USER_ID, + //中文名 + CHINESE_NAME, + //身份证号 + ID_CARD, + //座机号 + FIXED_PHONE, + //手机号 + MOBILE_PHONE, + //地址 + ADDRESS, + //电子邮件 + EMAIL, + //密码 + PASSWORD; + } + + /** + * 【用户id】不对外提供userId + * @return 脱敏后的主键 + */ + public static Long userId(){ + return 0L; + } + + /** + * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李** + * + * @param fullName 姓名 + * @return 脱敏后的姓名 + */ + public static String chineseName(String fullName) { + if (StrUtil.isBlank(fullName)) { + return ""; + } + String name = StrUtil.left(fullName, 1); + return StrUtil.rightPad(name, StrUtil.length(fullName), "*"); + } + + /** + * 【身份证号】前1位 和后2位 + * + * @param idCardNum 身份证 + * @param front 保留:前面的front位;从:1开始; + * @param end 保留:后面的end位;从1:开始; + * @return 脱敏后的身份证 + */ + public static String idCardNum(String idCardNum, int front, int end) { + //身份证不能为空 + if (StrUtil.isEmpty(idCardNum)) { + return ""; + } + //需要截取的长度不能大于身份证号长度 + if ((front + end) > idCardNum.length()) { + return ""; + } + //需要截取的不能小于0 + if (front < 0 || end < 0) { + return ""; + } + //计算*的数量 + int asteriskCount = idCardNum.length() - (front + end); + StringBuffer asteriskStr = new StringBuffer(); + for (int i = 0; i < asteriskCount; i++) { + asteriskStr.append("*"); + } + String regex = "(\\w{" + String.valueOf(front) + "})(\\w+)(\\w{" + String.valueOf(end) + "})"; + return idCardNum.replaceAll(regex, "$1" + asteriskStr + "$3"); + } + + /** + * 【固定电话 前四位,后两位 + * + * @param num 固定电话 + * @return 脱敏后的固定电话; + */ + public static String fixedPhone(String num) { + if (StrUtil.isBlank(num)) { + return ""; + } + return StrUtil.left(num, 4).concat(StrUtil.removePrefix(StrUtil.leftPad(StrUtil.right(num, 2), StrUtil.length(num), "*"), "****")); + } + + /** + * 【手机号码】前三位,后4位,其他隐藏,比如135****2210 + * + * @param num 移动电话; + * @return 脱敏后的移动电话; + */ + public static String mobilePhone(String num) { + if (StrUtil.isBlank(num)) { + return ""; + } + return StrUtil.left(num, 3).concat(StrUtil.removePrefix(StrUtil.leftPad(StrUtil.right(num, 4), StrUtil.length(num), "*"), "***")); + } + + /** + * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区**** + * + * @param address 家庭住址 + * @param sensitiveSize 敏感信息长度 + * @return 脱敏后的家庭地址 + */ + public static String address(String address, int sensitiveSize) { + if (StrUtil.isBlank(address)) { + return ""; + } + int length = StrUtil.length(address); + return StrUtil.rightPad(StrUtil.left(address, length - sensitiveSize), length, "*"); + } + + /** + * 【电子邮箱 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com> + * + * @param email 邮箱 + * @return 脱敏后的邮箱 + */ + public static String email(String email) { + if (StrUtil.isBlank(email)) { + return ""; + } + int index = StrUtil.indexOf(email, '@'); + if (index <= 1) { + return email; + } else { + return StrUtil.rightPad(StrUtil.left(email, 1), index, "*").concat(StrUtil.mid(email, index, StrUtil.length(email))); + } + } + + /** + * 【密码】密码的全部字符都用*代替,比如:****** + * + * @param password 密码 + * @return 脱敏后的邮箱 + */ + public static String password(String password) { + if (StrUtil.isBlank(password)) { + return ""; + } + String pwd = StrUtil.left(password, 0); + return StrUtil.rightPad(pwd, StrUtil.length(password), "*"); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/DesensitizedUtilsTest.java b/hutool-core/src/test/java/cn/hutool/core/util/DesensitizedUtilsTest.java new file mode 100644 index 000000000..cd5853dc2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/util/DesensitizedUtilsTest.java @@ -0,0 +1,58 @@ +package cn.hutool.core.util; + +import org.junit.Assert; +import org.junit.Test; + +/** + * 脱敏工具类 DesensitizedUtils 安全测试 + * @author dazer and nuesoft + * @date 2021/3/20 22:34 + * @see DesensitizedUtils + */ +public class DesensitizedUtilsTest { + + @Test + public void userIdTest() { + Assert.assertEquals(Long.valueOf(0L), DesensitizedUtils.userId()); + } + + @Test + public void chineseNameTest() { + Assert.assertEquals("段**", DesensitizedUtils.chineseName("段正淳")); + } + + @Test + public void idCardNumTest() { + Assert.assertEquals("5***************1X", DesensitizedUtils.idCardNum("51343620000320711X", 1, 2)); + } + + @Test + public void fixedPhoneTest() { + Assert.assertEquals("0915*****79", DesensitizedUtils.fixedPhone("09157518479")); + } + + @Test + public void mobilePhoneTest() { + Assert.assertEquals("180****1999", DesensitizedUtils.mobilePhone("18049531999")); + } + + @Test + public void addressTest() { + Assert.assertEquals("北京市海淀区马连洼街*****", DesensitizedUtils.address("北京市海淀区马连洼街道289号", 5)); + Assert.assertEquals("***************", DesensitizedUtils.address("北京市海淀区马连洼街道289号", 50)); + Assert.assertEquals("北京市海淀区马连洼街道289号", DesensitizedUtils.address("北京市海淀区马连洼街道289号", 0)); + Assert.assertEquals("北京市海淀区马连洼街道289号", DesensitizedUtils.address("北京市海淀区马连洼街道289号", -1)); + } + + @Test + public void emailTest() { + Assert.assertEquals("d********@126.com", DesensitizedUtils.email("duandazhi@126.com")); + Assert.assertEquals("d********@gmail.com.cn", DesensitizedUtils.email("duandazhi@gmail.com.cn")); + Assert.assertEquals("d*************@gmail.com.cn", DesensitizedUtils.email("duandazhi-jack@gmail.com.cn")); + } + + @Test + public void passwordTest() { + Assert.assertEquals("**********", DesensitizedUtils.password("1234567890")); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 1690291a5..7a1e18cfb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -192,6 +192,10 @@ public class StrUtilTest { String a = "1039"; String result = StrUtil.padPre(a, 8, "0"); //在字符串1039前补4个0 Assert.assertEquals("00001039", result); + + String aa = "1039"; + String result1 = StrUtil.padPre(aa, -1, "0"); //在字符串1039前补4个0 + Assert.assertEquals("103", result1); } @Test @@ -403,6 +407,7 @@ public class StrUtilTest { Assert.assertNull(StrUtil.padAfter(null, 10, ' ')); Assert.assertEquals("100", StrUtil.padAfter("1", 3, '0')); Assert.assertEquals("23", StrUtil.padAfter("123", 2, '0')); + Assert.assertEquals("23", StrUtil.padAfter("123", -1, '0')); Assert.assertNull(StrUtil.padAfter(null, 10, "ABC")); Assert.assertEquals("1AB", StrUtil.padAfter("1", 3, "ABC")); @@ -492,4 +497,109 @@ public class StrUtilTest { final String ret = StrUtil.indexedFormat("this is {0} for {1}", "a", 1000); Assert.assertEquals("this is a for 1,000", ret); } + + @Test + public void leftTest() { + Assert.assertNull(StrUtil.left(null, 1)); + Assert.assertEquals("", StrUtil.left("neu", -1)); + Assert.assertEquals("", StrUtil.left("", 1)); + Assert.assertEquals("", StrUtil.left("neu", 0)); + Assert.assertEquals("ne", StrUtil.left("neu", 2)); + Assert.assertEquals("neu", StrUtil.left("neu", 4)); + } + + @Test + public void leftPadTest() { + Assert.assertNull(StrUtil.leftPad(null, 1)); + Assert.assertEquals("", StrUtil.leftPad("", -1)); + Assert.assertEquals(" ", StrUtil.leftPad("", 3)); + Assert.assertEquals("bat", StrUtil.leftPad("bat", -1)); + Assert.assertEquals("bat", StrUtil.leftPad("bat", 1)); + Assert.assertEquals("bat", StrUtil.leftPad("bat", 3)); + Assert.assertEquals(" bat", StrUtil.leftPad("bat", 5)); + + Assert.assertNull(StrUtil.leftPad(null, 1, "z")); + Assert.assertEquals("", StrUtil.leftPad("", -1, "z")); + Assert.assertEquals("zzz", StrUtil.leftPad("", 3, "z")); + Assert.assertEquals("bat", StrUtil.leftPad("bat", -1, "z")); + Assert.assertEquals("bat", StrUtil.leftPad("bat", 1, "z")); + Assert.assertEquals("bat", StrUtil.leftPad("bat", 3, "z")); + Assert.assertEquals("zzbat", StrUtil.leftPad("bat", 5, "z")); + } + + @Test + public void rightTest() { + Assert.assertNull(StrUtil.right(null, 1)); + Assert.assertEquals("", StrUtil.right("neu", -1)); + Assert.assertEquals("", StrUtil.right("", 1)); + Assert.assertEquals("", StrUtil.right("neu", 0)); + Assert.assertEquals("eu", StrUtil.right("neu", 2)); + Assert.assertEquals("neu", StrUtil.right("neu", 4)); + } + + @Test + public void rightPadTest() { + Assert.assertNull(StrUtil.rightPad(null, 1)); + Assert.assertEquals(" ", StrUtil.rightPad("", 3)); + Assert.assertEquals("bat", StrUtil.rightPad("bat", 3)); + Assert.assertEquals("bat ", StrUtil.rightPad("bat", 5)); + Assert.assertEquals("bat", StrUtil.rightPad("bat", 1)); + Assert.assertEquals("bat", StrUtil.rightPad("bat", -1)); + + Assert.assertNull(StrUtil.rightPad(null, 1, 'z')); + Assert.assertEquals("zzz", StrUtil.rightPad("", 3, 'z')); + Assert.assertEquals("bat", StrUtil.rightPad("bat", 3, 'z')); + Assert.assertEquals("batzz", StrUtil.rightPad("bat", 5, 'z')); + Assert.assertEquals("bat", StrUtil.rightPad("bat", 1, 'z')); + Assert.assertEquals("bat", StrUtil.rightPad("bat", -1, 'z')); + + Assert.assertNull(StrUtil.rightPad(null, 1, "z")); + Assert.assertEquals("zzz", StrUtil.rightPad("", 3, "z")); + Assert.assertEquals("bat", StrUtil.rightPad("bat", 3, "z")); + Assert.assertEquals("batzz", StrUtil.rightPad("bat", 5, "z")); + Assert.assertEquals("bat", StrUtil.rightPad("bat", 1, "z")); + Assert.assertEquals("bat", StrUtil.rightPad("bat", -1, "z")); + } + + @Test + public void midTest() { + Assert.assertNull(StrUtil.mid(null, 1, 1)); + Assert.assertEquals("", StrUtil.mid("abc", 1, -1)); + Assert.assertEquals("", StrUtil.mid("", 0, 1)); + Assert.assertEquals("ab", StrUtil.mid("abc", 0, 2)); + Assert.assertEquals("abc", StrUtil.mid("abc", 0, 4)); + Assert.assertEquals("c", StrUtil.mid("abc", 2, 4)); + Assert.assertEquals("", StrUtil.mid("abc", 4, 2)); + Assert.assertEquals("ab", StrUtil.mid("abc", -2, 2)); + } + + @Test + public void hideTest() { + Assert.assertNull(StrUtil.hide(null, 1, 1)); + Assert.assertEquals("", StrUtil.hide("", 1, 1)); + Assert.assertEquals("****duan@163.com", StrUtil.hide("jackduan@163.com", -1, 4)); + Assert.assertEquals("ja*kduan@163.com", StrUtil.hide("jackduan@163.com", 2, 3)); + Assert.assertEquals("jackduan@163.com", StrUtil.hide("jackduan@163.com", 3, 2)); + Assert.assertEquals("jackduan@163.com", StrUtil.hide("jackduan@163.com", 16, 16)); + Assert.assertEquals("jackduan@163.com", StrUtil.hide("jackduan@163.com", 16, 17)); + + + Assert.assertEquals("0", StrUtil.hide("100", DesensitizedUtils.DesensitizedType.USER_ID)); + Assert.assertEquals("段**", StrUtil.hide("段正淳", DesensitizedUtils.DesensitizedType.CHINESE_NAME)); + Assert.assertEquals("5***************1X", StrUtil.hide("51343620000320711X", DesensitizedUtils.DesensitizedType.ID_CARD)); + Assert.assertEquals("0915*****79", StrUtil.hide("09157518479", DesensitizedUtils.DesensitizedType.FIXED_PHONE)); + Assert.assertEquals("180****1999", StrUtil.hide("18049531999", DesensitizedUtils.DesensitizedType.MOBILE_PHONE)); + Assert.assertEquals("北京市海淀区马********", StrUtil.hide("北京市海淀区马连洼街道289号", DesensitizedUtils.DesensitizedType.ADDRESS)); + Assert.assertEquals("d*************@gmail.com.cn", StrUtil.hide("duandazhi-jack@gmail.com.cn", DesensitizedUtils.DesensitizedType.EMAIL)); + Assert.assertEquals("**********", StrUtil.hide("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)); + + Assert.assertEquals("0", StrUtil.desensitized("100", DesensitizedUtils.DesensitizedType.USER_ID)); + Assert.assertEquals("段**", StrUtil.desensitized("段正淳", DesensitizedUtils.DesensitizedType.CHINESE_NAME)); + Assert.assertEquals("5***************1X", StrUtil.desensitized("51343620000320711X", DesensitizedUtils.DesensitizedType.ID_CARD)); + Assert.assertEquals("0915*****79", StrUtil.desensitized("09157518479", DesensitizedUtils.DesensitizedType.FIXED_PHONE)); + Assert.assertEquals("180****1999", StrUtil.desensitized("18049531999", DesensitizedUtils.DesensitizedType.MOBILE_PHONE)); + Assert.assertEquals("北京市海淀区马********", StrUtil.desensitized("北京市海淀区马连洼街道289号", DesensitizedUtils.DesensitizedType.ADDRESS)); + Assert.assertEquals("d*************@gmail.com.cn", StrUtil.desensitized("duandazhi-jack@gmail.com.cn", DesensitizedUtils.DesensitizedType.EMAIL)); + Assert.assertEquals("**********", StrUtil.desensitized("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)); + } } From f303afd37e06d26b38b37d9cfc1cc7f27a163677 Mon Sep 17 00:00:00 2001 From: duandazhi Date: Sat, 20 Mar 2021 23:34:34 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0@since=E5=92=8C@author?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/text/CharSequenceUtil.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 3330a06aa..5f5b5bd3f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -3642,6 +3642,7 @@ public class CharSequenceUtil { * StrUtil.hide("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)) = "**********" * * + * @author dazer and neusoft and qiaomu * @see DesensitizedUtils 如果需要自定义,脱敏规则,请使用该工具类; * @param str 字符串 * @param desensitizedType 脱敏类型;可以脱敏:用户id、中文名、身份证号、座机号、手机号、地址、电子邮件、密码 @@ -3697,6 +3698,7 @@ public class CharSequenceUtil { * StrUtil.desensitized("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)) = "**********" * * + * @author dazer and neusoft and qiaomu * @see DesensitizedUtils 如果需要自定义,脱敏规则,请使用该工具类; * @param str 字符串 * @param desensitizedType 脱敏类型;可以脱敏:用户id、中文名、身份证号、座机号、手机号、地址、电子邮件、密码 @@ -4375,8 +4377,9 @@ public class CharSequenceUtil { * * * @param str 从中获取最左边字符的字符串,可以为null - * @param len the length of the required String - * @return the left most characters, {@code null} if null String input + * @param len 要求的字符串长度 + * @return 返回最左边的字符串 + * @since 5.6.2 */ public static String left(final String str, final int len) { if (str == null) { @@ -4410,6 +4413,7 @@ public class CharSequenceUtil { * @param size 填充到size长度 * @return 如果不需要填充,请使用左填充字符串或原始字符串, * {@code null} if null String input + * @since 5.6.2 */ public static String leftPad(final String str, final int size) { return leftPad(str, size, ' '); @@ -4438,7 +4442,7 @@ public class CharSequenceUtil { * @return left padded String or original String if no padding is necessary, * 左侧填充字符串 or 如果不需要填充则返回原始字符串; * {@code null} if null String input; 如果输入字符串是null - * @since 2.0 + * @since 5.6.2 */ public static String leftPad(final String str, final int size, final char padChar) { if (str == null) { @@ -4476,6 +4480,7 @@ public class CharSequenceUtil { * @param padStr 要填充的字符串,如果为null或空,则将其视为单个空格 * @return 如果不需要填充,请使用左填充字符串或原始字符串, * {@code null} 如果为null字符串输入 + * @since 5.6.2 */ public static String leftPad(final String str, final int size, String padStr) { if (str == null) { @@ -4526,6 +4531,7 @@ public class CharSequenceUtil { * @param str 从中获取最右边字符的字符串,可以为null * @param len 所需字符串的长度 * @return 最右边的字符,{@code null},如果为null字符串输入 + * @since 5.6.2 */ public static String right(final String str, final int len) { if (str == null) { @@ -4557,6 +4563,7 @@ public class CharSequenceUtil { * @param size 填充的长度 * @return 如果不需要填充,则使用右填充的字符串或原始字符串, * {@code null} 如果为null字符串输入 + * @since 5.6.2 */ public static String rightPad(final String str, final int size) { return rightPad(str, size, ' '); @@ -4581,7 +4588,7 @@ public class CharSequenceUtil { * @param padChar 用来填充的字符串 * @return 返回 右侧填充字符串 or 如果不需要填充,则为原始String。 * {@code null} 如果为null字符串输入 - * @since 2.0 + * @since 5.6.2 */ public static String rightPad(final String str, final int size, final char padChar) { if (str == null) { @@ -4622,6 +4629,7 @@ public class CharSequenceUtil { * @return right padded String or original String if no padding is necessary, * 右侧填充字符串 or 如果不需要填充则返回原始字符串 * {@code null} if null String input + * @since 5.6.2 */ public static String rightPad(final String str, final int size, String padStr) { if (str == null) { @@ -4684,6 +4692,7 @@ public class CharSequenceUtil { * @param pos 其实位置,如果是负数则当做0; the position to start from, negative treated as zero * @param len 要求字符串的长度; the length of the required String * @return the middle characters, {@code null} if null String input + * @since 5.6.2 */ public static String mid(final String str, int pos, final int len) { if (str == null) { From 422c3de12d86a62fb18952fb0613f046e7e0fe3e Mon Sep 17 00:00:00 2001 From: duandazhi Date: Sat, 20 Mar 2021 23:46:56 +0800 Subject: [PATCH 3/3] =?UTF-8?q?Validator=20=E9=A9=BE=E9=A9=B6=E8=AF=81?= =?UTF-8?q?=E6=A1=A3=E6=A1=88=E7=BC=96=E5=8F=B7=20=E5=92=8C=20=E5=8F=91?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E8=BD=A6=E6=9E=B6=E5=8F=B7=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0@author=20=E6=96=B9=E4=BE=BF=E5=90=8E=E6=9C=9F?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E7=BB=B4=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/core/lang/Validator.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java index 96f1f1f7e..bca1d0ae5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java @@ -1148,6 +1148,8 @@ public class Validator { /** * 验证是否为车架号;别名:行驶证编号 车辆识别代号 车辆识别码 * + * @author dazer and ourslook + * * @param value 值,17位车架号;形如:LSJA24U62JG269225、LDC613P23A1305189 * @return 是否为车架号 * @since 5.6.3 @@ -1159,6 +1161,8 @@ public class Validator { /** * 验证是否为车架号;别名:行驶证编号 车辆识别代号 车辆识别码 * + * @author dazer and ourslook + * * @param 字符串类型 * @param value 值 * @param errorMsg 验证错误的信息 @@ -1177,6 +1181,8 @@ public class Validator { * 验证是否为驾驶证 别名:驾驶证档案编号、行驶证编号 * 仅限:中国驾驶证档案编号 * + * @author dazer and ourslook + * * @param value 值,12位数字字符串,eg:430101758218 * @return 是否为档案编号 * @since 5.6.3 @@ -1188,6 +1194,8 @@ public class Validator { /** * 验证是否为驾驶证 别名:驾驶证档案编号、行驶证编号 * + * @author dazer and ourslook + * * @param 字符串类型 * @param value 值 * @param errorMsg 验证错误的信息