使用自定义注解加密实体中手机号码字段

本文介绍了一种使用自定义注解加密手机号码字段的方法,通过在实体对象中加注解,实现对手机号中间四位的加密,适用于数据库查询结果展示到前端时的隐私保护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

通常我们数据库中带有用户手机号的数据,在展示给前端的时候,都需要进行部分加密,也就是真实手机号为15066668888的数据在加密返回给前端之后,应为150****8888
关于这个问题,解决方案很多很灵活,这里我写一个使用自定义注解来加密手机号码字段的小栗子

基本思路

1.首先我们把数据从数据库中查询出来,放到实体对象中,在实体对象中需要被加密的字段上面加上我们自定义的注解
2.在业务层中,我们拿到实体对象或实体对象的集合,之后调用加密方法,把加了注解的字段进行加密,并且返回新的实体对象或新的实体对象集合
3.我们拿到新的结果之后,就可以按照之前的逻辑进行处理了。我自认为这样的操作对业务层的代码入侵不是很大

代码示例

注意:本示例依赖Hutool工具类hutool-core
1.自定义加密注解
/**
 * 虚拟手机号注解<br>
 * 在实体中需要被加密的字段上使用该注解(目前仅用于加密手机号码中间4位)<br>
 * conditionField参数可以为空,为空时,强制解密注解字段<br>
 * conditionField参数会告知程序,依据这个字段的数值,来决定是否加密注解字段
 *
 * @author MrXionGe
 * @since 2019/2/22
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface VirtualMobile {

    /**
     * 加密条件字段<br>
     * 这个参数所指向的字段需要是integer类型<br>
     * 条件字段为1执行加密,为0不执行加密(如果这个字段是别的整数数值,强制加密;如果是别的类型的值,打印异常信息,并返回原值)<br>
     * 若该参数是一个不存在的字段,则强制加密
     *
     * @return String
     */
    String conditionField() default "";

}
2.注解处理器(这里写了静态方法,调用方法时执行加密操作)
/**
 * @author MrXionGe
 * @since 2019/2/22
 */
public class VirtualMobileProcessor {

    /**
     * 加密Bean对象中的指定字段<br>
     * 用于加密实体对象中的手机号码字段
     *
     * @param object Bean对象
     * @param clazz  对象类型
     * @return 加密之后的JavaBean
     */
    public static <T> T cryptoMobile(T object, Class<T> clazz) {
        String cryptoField;
        String conditionField;
        //判断是否有打了注解的字段
        for (Field declaredField : clazz.getDeclaredFields()) {
            VirtualMobile annotation = declaredField.getAnnotation(VirtualMobile.class);
            //找到注解字段,则执行字段加密
            if (Validator.isNotNull(annotation)) {
                cryptoField = declaredField.getName();
                conditionField = annotation.conditionField();
                return copyBean(cryptoField, conditionField, object, clazz);
            }
        }
        //没有找到注解字段,直接返回传入的对象
        return Convert.convert(clazz, object);
    }

    /**
     * 加密集合中每个对象的指定字段<br>
     * 用于加密实体对象中的手机号码字段
     *
     * @param collection Bean对象集合
     * @param clazz      集合中的对象类型
     * @return 加密之后的JavaBean集合
     */
    public static <T> Collection<T> cryptoMobileForList(Collection<T> collection, Class<T> clazz) {
        return collection.stream().map(t -> cryptoMobile(t, clazz)).collect(Collectors.toList());
    }

    /**
     * 加密Bean对象<br>
     * 传入需要被加密的Bean,返回加密之后的Bean
     *
     * @param cryptoField    需要被加密的字段
     * @param conditionField 参考是否加密的条件字段
     * @param object         Bean对象
     * @param clazz          对象类型
     * @return 加密之后的JavaBean
     */
    private static <T> T copyBean(String cryptoField, String conditionField, T object, Class<T> clazz) {
        try {
            //cryptoField参数为空,直接返回原对象
            if (Validator.isEmpty(cryptoField)) {
                return Convert.convert(clazz, object);
            }
            //判断条件字段
            if (Validator.isNotEmpty(conditionField)) {
                for (Field declaredField : clazz.getDeclaredFields()) {
                    declaredField.setAccessible(true);
                    //不加密
                    if (declaredField.getName().equals(conditionField) && Integer.valueOf(String.valueOf(declaredField.get(object))) == 0) {
                        return Convert.convert(clazz, object);
                    }
                    //加密
                    if (declaredField.getName().equals(conditionField) && Integer.valueOf(String.valueOf(declaredField.get(object))) == 1) {
                        return cryptoBean(cryptoField, object, clazz);
                    }
                }
            }
            //条件字段为空,强制加密
            return cryptoBean(cryptoField, object, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            //发生异常,返回原对象
            return Convert.convert(clazz, object);
        }
    }

    /**
     * 加密Bean中的指定字段
     *
     * @param cryptoField 需要被加密的字段
     * @param object      Bean对象
     * @param clazz       对象类型
     * @return 加密之后的JavaBean
     */
    private static <T> T cryptoBean(String cryptoField, T object, Class<T> clazz) {
        try {
            for (Field declaredField : clazz.getDeclaredFields()) {
                declaredField.setAccessible(true);
                if (declaredField.getName().equals(cryptoField)) {
                    BeanUtil.setFieldValue(object, cryptoField, createVirtualMobile(String.valueOf(declaredField.get(object))));
                }
            }
            return Convert.convert(clazz, object);
        } catch (Exception e) {
            e.printStackTrace();
            return Convert.convert(clazz, object);
        }
    }

    /**
     * 加密手机号码中间4位<br>
     * 如果不是11位手机号码,直接返回原值
     *
     * @param mobile 加密前的手机号码
     * @return 加密后的手机号码
     */
    private static String createVirtualMobile(String mobile) {
        try {
            //转换成Long如果异常或者不是11位,就说明不是手机号码,直接返回传入的对象
            Long.valueOf(mobile);
            if (mobile.length() != 11) {
                return mobile;
            }
            return StrUtil.hide(mobile, 3, 7);
        } catch (Exception e) {
            return mobile;
        }
    }
}

3.测试实体类
/**
 * @author MrXionGe
 * @since 2019/2/22
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
class Student {

    private Long id;

    @VirtualMobile(conditionField = "cryptoStatus")
    private String mobile;

    private Integer cryptoStatus;

    private LocalDateTime createTime;
}

4.测试类
/**
 * @author MrXionGe
 * @since 2019/2/22
 */
public class Test {

    public static void main(String[] args) {
        //单对象测试
        System.out.println(cryptoMobile(new Student(randomLong(11111111L, 99999999L), String.valueOf(randomLong(13011111111L, 13099999999L)),
                1, LocalDateTime.now()), Student.class));
        //集合测试
        List<Student> studentList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Student student = new Student();
            student.setId(randomLong(11111111L, 99999999L));
            student.setMobile(String.valueOf(randomLong(13011111111L, 13099999999L)));
            student.setCryptoStatus(1);
            student.setCreateTime(LocalDateTime.now());
            studentList.add(student);
        }
        long startMillis = System.currentTimeMillis();
        Collection<Student> students = cryptoMobileForList(studentList, Student.class);
        long stopMillis = System.currentTimeMillis();
        System.out.println(stopMillis - startMillis + "ms");
        System.out.println(students);
    }
}

测试

测试类中,我们有单个对象的加密测试,也有集合的加密测试,我们一起跑一下

在这里插入图片描述

我们可以看到,手机号字段加密成功了

性能

我们测试一下加密10万数据所需要的时间
测试代码如下图

在这里插入图片描述

我测试五次分别是 267ms,241ms,254ms,272ms,257ms
我认为这样的处理速度还是可以接受的
以上就是使用自定义注解加密手机号码字段的全部示例,如果我的代码中有错误或您有更优的写法,欢迎大家吐槽
<think>首先,用户的问题是关于Java实体类中List类型字段的存储。用户希望知道是否可以使用注解将List类型字段以字符串形式(to_string)存入数据库表中。 回顾引用内容: - 引用[1]:展示了一个JPA转换器,将List转换为JSON字符串存入数据库,并从字符串转换回List。 - 引用[2]:类似地,提到将List序列化为字符串存入数据库,并使用JSON解析取出。 - 引用[3]和[4]:涉及字段加密和解密,使用注解。 - 引用[5]:描述了一个方案,使用数据库varchar类型、MyBatisPlus的字段处理器和@TableField注解来处理List字段的存储。 用户的需求是:使用注解实体类中的List类型字段以to_string形式存入数据库。这里的"to_string"可能意味着将List转换为字符串表示,比如JSON字符串或逗号分隔的字符串。 系统指令要求: - 所有行内数学表达式用$...$。 - 独立公式用$$...$$并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 我的回答结构: 1. **理解需求**:解释用户的问题。 2. **解决方案**:提供基于注解的实现方式。 3. **代码示例**:给出具体代码。 4. **注意事项**:讨论潜在问题。 5. **相关问题**:生成相关问题。 关键点: - 使用JPA或MyBatisPlus等框架的注解。 - 参考引用[1]和[5],它们展示了转换器。 - 引用[1]使用自定义转换器,引用[5]使用MyBatisPlus的注解。 解决方案: - 在JPA中,可以使用AttributeConverter接口实现自定义转换。 - 在MyBatisPlus中,可以使用@TableField注解配合字段处理器。 - 用户提到"注解",所以可以展示注解方式。 生成相关问题: - 基于主题,如性能、优化、应用场景。 最终回答: - 用中文。 - 确保LaTeX正确,但这里可能没有数学公式,所以不需要。 - 添加引用标识。 回答大纲: 1. 介绍:是的,可以使用注解实现。 2. JPA方式:使用@Convert注解和AttributeConverter。 - 代码示例。 3. MyBatisPlus方式:使用@TableField注解自定义类型处理器。 - 代码示例。 4. 注意事项:如字符串格式、性能。 5. 相关问题。</think>### Java 实体类 List 类型字段使用注解实现字符串存储 是的,在 Java 中,您可以通过自定义注解结合转换器(如 JPA 的 `AttributeConverter` 或 MyBatis Plus 的 `TypeHandler`),将实体类中的 `List` 类型字段自动转换为字符串(如 JSON 或逗号分隔格式)存入数据库。这避免了硬编码,提升了代码可维护性。以下是两种主流实现方式,参考了引用中的最佳实践。 #### 1. **使用 JPA 的 `AttributeConverter` 和 `@Convert` 注解** JPA(Java Persistence API)提供了 `AttributeConverter` 接口,允许您定义字段与数据库列的转换逻辑。结合 `@Convert` 注解,您可以在实体类中直接标记 `List` 字段,实现自动转换。 - **优点**:标准化高,支持所有 JPA 实现(如 Hibernate)。 - **缺点**:需手动实现转换逻辑。 **代码示例**: ```java import javax.persistence.*; import com.alibaba.fastjson.JSON; import java.util.List; // 步骤 1: 定义转换器类(实现 AttributeConverter) @Converter(autoApply = true) public class ListToStringConverter implements AttributeConverter<List<String>, String> { @Override public String convertToDatabaseColumn(List<String> list) { return JSON.toJSONString(list); // List 转为 JSON 字符串 } @Override public List<String> convertToEntityAttribute(String dbData) { return JSON.parseArray(dbData, String.class); // 字符串转回 List } } // 步骤 2: 在实体类中使用 @Convert 注解 @Entity @Table(name = "user") public class User { @Id private Long id; @Convert(converter = ListToStringConverter.class) private List<String> hobbies; // 该字段将以 JSON 字符串存入数据库 } ``` - **数据库映射**:`hobbies` 字段在数据库中定义为 `VARCHAR` 或 `TEXT` 类型,存储格式如 `["reading","sports"]`。 - **引用说明**:此方法基于引用[1]中的 `JpaConverterListJson` 实现,但简化了泛型处理以适应通用场景[^1]。 #### 2. **使用 MyBatis Plus 的 `@TableField` 注解和 `TypeHandler`** 如果您使用 MyBatis Plus,可以通过自定义 `TypeHandler` 和 `@TableField` 注解实现类似功能,无需依赖 JPA。 - **优点**:轻量级,适合 MyBatis 项目。 - **缺点**:需配置 MyBatis Plus 的全局处理器。 **代码示例**: ```java import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.core.handlers.MyBatisEnumTypeHandler; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; // 步骤 1: 定义 TypeHandler(处理 List 与 String 的转换) public class ListTypeHandler extends BaseTypeHandler<List<String>> { @Override public void setNonNullParameter(PreparedStatement ps, int i, List<String> list, JdbcType jdbcType) throws SQLException { ps.setString(i, String.join(",", list)); // List 转为逗号分隔字符串 } @Override public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException { String dbValue = rs.getString(columnName); return dbValue != null ? Arrays.asList(dbValue.split(",")) : null; // 字符串转回 List } // 其他重写方法(略) } // 步骤 2: 在实体类中使用 @TableField 注解 @TableName("product") public class Product { @TableId private Long id; @TableField(typeHandler = ListTypeHandler.class) private List<String> tags; // 该字段将以逗号分隔字符串存入数据库 } ``` - **数据库映射**:`tags` 字段在数据库中定义为 `VARCHAR`,存储格式如 `"tech,book,digital"`。 - **引用说明**:此方法参考引用[5]中的设计,利用 MyBatis Plus 的注解简化字段处理[^5]。 #### 注意事项 - **字符串格式选择**:JSON 格式(如 `["a","b"]`)支持复杂对象但占用空间较大;逗号分隔(如 `"a,b"`)简单但无法处理含逗号的数据。推荐 JSON 以确保兼容性。 - **性能影响**:频繁转换可能增加 CPU 开销,适用于中小规模数据。若列表元素过多,考虑改用关联表设计。 - **框架依赖**:JPA 方案需 Hibernate 等实现;MyBatis Plus 方案需集成 MyBatis。 - **加密扩展**:如需安全存储,可结合引用[3][^3]或[4][^4]的加密注解,在转换前对字符串加密。 #### 总结 通过自定义转换器(`AttributeConverter` 或 `TypeHandler`)和注解(`@Convert` 或 `@TableField`),您可以无缝将 `List` 字段存储为数据库字符串。推荐优先使用 JPA 方案,因其标准化程度高。实际应用中,确保数据库列类型为字符串(如 `VARCHAR(255)`),并在转换逻辑中处理异常(如空值或格式错误)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值