如何优雅地实现手机号脱敏

如何实现手机号脱敏

最近想起了上次面试官的一个问题:你们的电商项目里面很多用户信息、订单信息,是如何实现手机号、证件号脱敏的?下面给大家介绍如下两种方式

方法一

由于SpringBoot默认整合了JSON序列化器Jackson,我们只需自定义一个序列化器并继承JsonSerializer即可。

public class PhoneJsonSerializer extends JsonSerializer {



    @Override

    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {

        if (value != null && value instanceof String){

            if (!Objects.equals(value,"") && value.toString().length() > 5){

                String phone = value.toString();

                phone = phone.substring(0, 3) + "******" + phone.substring(phone.length() - 2);

                gen.writeString(phone);

            } else {

                gen.writeObject(value);

            }

        } else {

            gen.writeObject(value);

        }

    }



}

Jackson注解

Jackson自带很多个注解,由于针对手机号进行脱敏处理,所以针对指定的手机号字段,加上其中的@JsonSerialize注解,通过反射找到指定的序列化器

@Data

public class User {

    @ApiModelProperty("年龄")

    private Integer age;



    @ApiModelProperty("姓名")

    private String name;



    @ApiModelProperty("手机号")

    @JsonSerialize(using = PhoneJsonSerializer.class)

    private String phone;

}

这样,我们就实现了手机号的****号返回,同理,对于姓名身份证号和银行卡号也能通过这样的方式处理。

方法二

1编写脱敏类型枚举类,满足不同场景的处理

public enum SensitiveEnum {



    /**

     * 中文名

     */

    CHINESE_NAME,



    /**

     * 身份证号

     */

    ID_CARD,



    /**

     * 座机号

     */

    FIXED_PHONE,



    /**

     * 手机号

     */

    MOBILE_PHONE,



    /**

     * 地址

     */

    ADDRESS,



    /**

     * 电子邮件

     */

    EMAIL,



    /**

     * 银行卡

     */

    BANK_CARD,



    /**

     * 公司开户银行联号

     */

CNAPS_CODE



}

2编写脱敏注解类

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;



@Retention(RetentionPolicy.RUNTIME)

@JacksonAnnotationsInside

@JsonSerialize(using = SensitiveSerialize.class)

public @interface SensitiveWrapped {



    /**

     * 脱敏类型

     * @return

     */

    SensitiveEnum value();

}

3编写脱敏序列化类

import com.fasterxml.jackson.core.JsonGenerator;

import com.fasterxml.jackson.databind.BeanProperty;

import com.fasterxml.jackson.databind.JsonMappingException;

import com.fasterxml.jackson.databind.JsonSerializer;

import com.fasterxml.jackson.databind.SerializerProvider;

import com.fasterxml.jackson.databind.ser.ContextualSerializer;

import java.io.IOException;

import java.util.Objects;



public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {



    /**

     * 脱敏类型

     */

    private SensitiveEnum type;



  

    @Override

    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {

        switch (this.type) {

            case CHINESE_NAME: {

                jsonGenerator.writeString(SensitiveInfoUtils.chineseName(s));

                break;

            }

            case ID_CARD: {

                jsonGenerator.writeString(SensitiveInfoUtils.idCardNum(s));

                break;

            }

            case FIXED_PHONE: {

                jsonGenerator.writeString(SensitiveInfoUtils.fixedPhone(s));

                break;

            }

            case MOBILE_PHONE: {

                jsonGenerator.writeString(SensitiveInfoUtils.mobilePhone(s));

                break;

            }

            case ADDRESS: {

                jsonGenerator.writeString(SensitiveInfoUtils.address(s, 4));

                break;

            }

            case EMAIL: {

                jsonGenerator.writeString(SensitiveInfoUtils.email(s));

                break;

            }

            case BANK_CARD: {

                jsonGenerator.writeString(SensitiveInfoUtils.bankCard(s));

                break;

            }

            case CNAPS_CODE: {

                jsonGenerator.writeString(SensitiveInfoUtils.cnapsCode(s));

                break;

            }

        }

    }



    @Override

    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {

        // 为空直接跳过

        if (beanProperty != null) {

            // 非 String 类直接跳过

            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {

                SensitiveWrapped sensitiveWrapped = beanProperty.getAnnotation(SensitiveWrapped.class);

                if (sensitiveWrapped == null) {

                    sensitiveWrapped = beanProperty.getContextAnnotation(SensitiveWrapped.class);

                }

                if (sensitiveWrapped != null) {

                    // 如果能得到注解,就将注解的 value 传入 SensitiveSerialize

                    return new SensitiveSerialize(sensitiveWrapped.value());

                }

            }

            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);

        }

        return serializerProvider.findNullValueSerializer(beanProperty);

    }



    public SensitiveSerialize() {}



    public SensitiveSerialize(final SensitiveEnum type) {

        this.type = type;

    }

}

其中createContextual的作用是通过字段已知的上下文信息定制JsonSerializer对象。

4、编写脱敏工具类

import org.apache.commons.lang3.StringUtils;



public class SensitiveInfoUtils {



    /**

     * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>

     */

    public static String chineseName(final String fullName) {

        if (StringUtils.isBlank(fullName)) {

            return "";

        }

        final String name = StringUtils.left(fullName, 1);

        return StringUtils.rightPad(name, StringUtils.length(fullName), "*");

    }



    /**

     * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>

     */

    public static String chineseName(final String familyName, final String givenName) {

        if (StringUtils.isBlank(familyName) || StringUtils.isBlank(givenName)) {

            return "";

        }

        return chineseName(familyName + givenName);

    }



    /**

     * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:420**********5762>

     */

    public static String idCardNum(final String id) {

        if (StringUtils.isBlank(id)) {

            return "";

        }



        return StringUtils.left(id, 3).concat(StringUtils

                .removeStart(StringUtils.leftPad(StringUtils.right(id, 4), StringUtils.length(id), "*"),

                        "***"));

    }



    /**

     * [固定电话] 后四位,其他隐藏<例子:****1234>

     */

    public static String fixedPhone(final String num) {

        if (StringUtils.isBlank(num)) {

            return "";

        }

        return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*");

    }



    /**

     * [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>

     */

    public static String mobilePhone(final String num) {

        if (StringUtils.isBlank(num)) {

            return "";

        }

        return StringUtils.left(num, 3).concat(StringUtils

                .removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"),

                        "***"));



    }



    /**

     * [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****>

     *

     * @param sensitiveSize 敏感信息长度

     */

    public static String address(final String address, final int sensitiveSize) {

        if (StringUtils.isBlank(address)) {

            return "";

        }

        final int length = StringUtils.length(address);

        return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");

    }



    /**

     * [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>

     */

    public static String email(final String email) {

        if (StringUtils.isBlank(email)) {

            return "";

        }

        final int index = StringUtils.indexOf(email, "@");

        if (index <= 1) {

            return email;

        } else {

            return StringUtils.rightPad(StringUtils.left(email, 1), index, "*")

                    .concat(StringUtils.mid(email, index, StringUtils.length(email)));

        }

    }



    /**

     * [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234>

     */

    public static String bankCard(final String cardNum) {

        if (StringUtils.isBlank(cardNum)) {

            return "";

        }

        return StringUtils.left(cardNum, 6).concat(StringUtils.removeStart(

                StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"),

                "******"));

    }



    /**

     * [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号<例子:12********>

     */

    public static String cnapsCode(final String code) {

        if (StringUtils.isBlank(code)) {

            return "";

        }

        return StringUtils.rightPad(StringUtils.left(code, 2), StringUtils.length(code), "*");

    }



}

5、编写测试实体类

最后,我们编写一个实体类UserEntity,看看转换后的效果如何?

@Data

public class UserEntity {



    /**

     * 用户ID

     */

    private Long userId;



    /**

     * 用户姓名

     */

    private String name;



    /**

     * 手机号

     */

    @SensitiveWrapped(SensitiveEnum.MOBILE_PHONE)

    private String mobile;



    /**

     * 身份证号码

     */

    @SensitiveWrapped(SensitiveEnum.ID_CARD)

    private String idCard;



    /**

     * 年龄

     */

    private String sex;



    /**

     * 性别

     */

    private int age;



}

6、测试程序如下

public class SensitiveDemo {



    public static void main(String[] args) throws JsonProcessingException {

        UserEntity userEntity = new UserEntity();

        userEntity.setUserId(1L);

        userEntity.setName("小王");

        userEntity.setMobile("16601178888");

        userEntity.setIdCard("412822199603220493");

        userEntity.setAge(20);

        userEntity.setSex("男");



        //通过jackson方式,将对象序列化成json字符串

        ObjectMapper objectMapper = new ObjectMapper();

        System.out.println(objectMapper.writeValueAsString(userEntity));

    }

}

7、结果如下

{"userId":1,"name":"小王","mobile":"166****8888","idCard":"412***********0493","sex":"男","age":20}

<think>好的,我现在需要回答用户关于中英文数据脱敏规则的问题。首先,我需要理解用户的需求。用户想了解中英文脱敏的具体规则、实现方法、示例以及相关规范。根据提供的引用资料,我需要整合这些信息,确保回答的结构清晰,并符合用户的要求。 首先,我应该区分中文和英文的脱敏规则。中文常见的敏感信息包括姓名、身份证号、手机号等,而英文可能涉及名字、社保号、信用卡号等。需要分别说明两者的处理方式。 接下来是实现方式。根据引用[1]、[2]、[3]、[4],脱敏通常使用正则表达式、字符串处理函数,或者通过注解和序列化的方式在框架中实现。例如,Java中的正则替换,JavaScript的正则处理,以及Spring Boot中的注解方式。 示例部分需要具体展示中英文的不同情况。比如中文姓名可能隐藏中间字符,而英文名字可能保留首字母并替换其余部分。手机号和身份证号的脱敏方式也需要举例说明,如保留前三位和后四位,中间用星号填充。 关于规范,需要提到相关的数据安全法规,如中国的《个人信息保护法》和欧盟的GDPR,强调合规性。同时,脱敏后的数据应保持业务可用性,比如部分隐藏后仍能用于验证,但无法还原原始信息。 需要注意的是,引用资料中的示例主要是中文处理,但用户还提到英文,所以需要补充英文的例子,比如信用卡号脱敏为****-****-****-1234。此外,要确保实现方式部分涵盖不同的编程语言,如JavaJavaScript,以及框架如Spring Boot优雅实现。 最后,生成相关问题需要考虑用户可能进一步询问的内容,如实现细节、法律差异、性能优化和验证方法等。要确保问题有针对性,帮助用户深入理解。 在整合过程中,必须遵循系统级指令,所有数学公式和代码块使用正确的格式,行内公式用$...$,独立公式用$$...$$,但当前问题不涉及数学公式,所以可能不需要。代码示例要正确使用语法,比如JavaJavaScript的正则替换。同时,引用标识要自然添加在相关段落末尾。 总结回答结构: 1. 脱敏规则:分中英文说明不同类型的敏感信息如何处理。 2. 实现方式:正则、字符串处理、框架注解等。 3. 示例:具体代码示例展示不同情况。 4. 规范:合规性、可用性、不可逆性。 5. 相关问题:围绕实现、规范、比较等生成。 需要检查是否覆盖了用户的所有需求,并确保引用的正确应用,比如引用[1]提到身份证脱敏,引用[4]提到JavaScript的正则,引用[2]和[3]提到Spring Boot优雅实现。同时,注意中英文差异,例如英文名字可能由多个部分组成,处理方式不同。 现在开始组织内容,确保每个部分清晰,符合用户要求的结构,并自然插入引用标识。</think>### 中英文数据脱敏规则与实现方法 #### 一、脱敏规则 1. **中文敏感信息** - **姓名**:保留首字,其余替换为星号,例如:张\*、王\*\*[^1]。 - **身份证号**:保留首3位和末4位,中间替换为星号,如`110***********1234`。 - **手机号**:保留前3位和后4位,如`178****5939`[^2]。 - **地址**:隐藏详细部分,如`北京市海淀区****`。 2. **英文敏感信息** - **姓名**:保留首字母和末字母,中间替换为星号,如`J***n`(John)或`A**a`(Anna)。 - **信用卡号**:保留末4位,如`****-****-****-1234`。 - **社保号(SSN)**:隐藏中间部分,如`123-**-4567`。 - **邮箱**:保留前缀首字母和域名,如`j***n@example.com`[^4]。 --- #### 二、实现方式 1. **正则表达式匹配与替换** - **通用方法**:通过正则定位敏感字段,替换为星号。 - **中文姓名**:`^(.).*$` → `$1**` - **英文姓名**:`(?<=\b[A-Za-z])[a-zA-Z]+(?=[A-Za-z]\b)` → `***` - **手机号**:`(\d{3})\d{4}(\d{4})` → `$1****$2`。 2. **编程语言内置函数** - **Java示例**(身份证脱敏): ```java public static String maskIdCard(String idCard) { return idCard.replaceAll("(?<=\\d{3})\\d(?=\\d{4})", "*"); } ``` - **JavaScript示例**(邮箱脱敏): ```javascript function maskEmail(email) { return email.replace(/(?<=.).(?=.*@)/g, '*'); } // 输入:john@example.com → 输出:j***n@example.com ``` 3. **框架级注解(如Spring Boot)** - 使用自定义注解`@MaskField`标记需脱敏字段,结合序列化器统一处理: ```java @MaskField(MaskEnum.PHONE) private String phone; // 自动脱敏为178****5939[^3] ``` --- #### 三、示例对比 | 类型 | 原始数据 | 脱敏后结果 | 实现方式 | |------------|--------------------|---------------------|---------------------------| | 中文姓名 | 张三丰 | 张** | `String.substring` + 替换 | | 英文姓名 | John Smith | J*** S**** | 正则匹配中间字符 | | 手机号 | 17812345939 | 178****5939 | 正则分组替换 | | 身份证号 | 110101199001011234 | 110***********1234 | 保留首尾,中间替换 | | 信用卡号 | 4111111111111234 | ****-****-****-1234| 截取末4位,其余替换 | --- #### 四、规范与注意事项 1. **合规性** - 需符合《个人信息保护法》(中国)和GDPR(欧盟),如脱敏后数据不可逆[^3]。 2. **业务可用性** - 部分场景需保留部分信息,如手机号后4位用于用户自验证。 3. **多语言适配** - 英文需考虑大小写、空格分隔(如`John Doe`),中文需处理多字节字符。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值