自定义脱敏表示注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Mark {

/**
* 是否开启,默认开启|可以灵活关闭注解能力
* true:开启
* false:关闭
*
* @return
*/
boolean on() default true;

/**
* 注解标识字段的脱敏类型
*
* @return
* @see MarkStrategyEnum 脱敏枚举
*/
MarkStrategyEnum markType();

}

策略枚举规范接口

1
2
3
4
5
6
7
public interface IMark {

Logger logger = LoggerFactory.getLogger(IMark.class);

String mark(String source);

}

脱敏规则:

1
2
3
4
5
6
7
8
9
10
脱敏规则如下:
手机:前3后4显示 ,其他*号替换 例如:186****1234
身份证:前6后4,其他*号替换 例如:412828****0412
姓名:前1后* ,后面加2个* 例如:王** 启** 胡**
地址:
1. 前4后4,其他*号替换 例如:杭州市余****第12楼
2. 如果小于等于8位,则显示4位后面补4个* 例如: 杭州市余****
3. 小于等于4位 则显示全部后卫补充4个* 例如: 杭州****
邮箱:前3,后@,其他*号替换 例如:980****@qq.com
银行卡:前6后4,其他*号替换 例如:622688 ****622

脱敏策略枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
public enum MarkStrategyEnum implements IMark {
// 前3后4显示 ,其他*号替换 例如:186****1234
PHONE("phone", "手机号码") {
@Override
public String mark(String source) {
try {

if (StringUtils.isEmpty(source)) {
logger.info("待脱敏的手机号字段为空,不处理.");
return source;
}

String left = source.substring(0, 3);
String right = source.substring(source.length() - 4);

return left + "****" + right;

} catch (Exception e) {
logger.warn("手机号字段脱敏错误,异常收敛并返回原数据,不影响主业务流程|Param:{}|e:", source, e);
return source;
}
}
},

// 前6后4,其他*号替换 例如:412828****0412
ID_CARD("id_card", "身份证号") {
@Override
public String mark(String source) {
try {

if (StringUtils.isEmpty(source)) {
logger.info("待脱敏的身份证号字段为空,不处理.");
return source;
}

String left = source.substring(0, 6);
String right = source.substring(source.length() - 4);

return left + "****" + right;

} catch (Exception e) {
logger.warn("身份证号字段脱敏错误,异常收敛并返回原数据,不影响主业务流程|Param:{}|e:", source, e);
return source;
}
}
},

// 前1后** 例如:王** 启** 胡**
NAME("name", "姓名") {
@Override
public String mark(String source) {
try {

if (StringUtils.isEmpty(source)) {
logger.info("待脱敏的姓名字段为空,不处理.");
return source;
}

String left = source.substring(0, 1);
return left + "**";

} catch (Exception e) {
logger.warn("姓名字段脱敏错误,异常收敛并返回原数据,不影响主业务流程|Param:{}|e:", source, e);
return source;
}
}
},

// 地址:
// 1. 前4后4,其他*号替换 例如:杭州市余****第12楼
// 2. 如果长度小于等于8位,则显示4位后面补4个* 例如: 杭州市余****
// 3. 长度小于等于4位 则显示全部后卫补充4个* 例如: 杭州****
ADDRESS("address", "地址") {
@Override
public String mark(String source) {
try {

if (StringUtils.isEmpty(source)) {
logger.info("待脱敏的地址字段为空,不处理.");
return source;
}

int length = source.length();
if (length > 8) {

String left = source.substring(0, 4);
String right = source.substring(source.length() - 4);

return left + "****" + right;

} else if (length >= 4 && length <= 8) {

String left = source.substring(0, 4);
return left + "****";

} else {

String left = source.substring(0, length);
return left + "****";

}

} catch (Exception e) {
logger.warn("地址字段脱敏错误,异常收敛并返回原数据,不影响主业务流程|Param:{}|e:", source, e);
return source;
}
}
},

// 邮箱:前3,后@,其他*号替换 例如:980****@qq.com
EMAIL("email", "邮箱") {
@Override
public String mark(String source) {
try {

if (StringUtils.isEmpty(source)) {
logger.info("待脱敏的邮箱字段为空,不处理.");
return source;
}

int index = source.indexOf("@");

String left = source.substring(0, 3);
String right = source.substring(index);

return left + "****" + right;

} catch (Exception e) {
logger.warn("邮箱字段脱敏错误,异常收敛并返回原数据,不影响主业务流程|Param:{}|e:", source, e);
return source;
}
}
},

// 银行卡:前6后4,其他*号替换 例如:622688 ****622
BANK_CARD("bank_card", "银行卡") {
@Override
public String mark(String source) {
try {

if (StringUtils.isEmpty(source)) {
logger.info("待脱敏的银行卡字段为空,不处理.");
return source;
}

String left = source.substring(0, 6);
String right = source.substring(source.length() - 4);
return left + "****" + right;

} catch (Exception e) {
logger.warn("银行卡字段脱敏错误,异常收敛并返回原数据,不影响主业务流程|Param:{}|e:", source, e);
return source;
}
}
},
;

/**
* 掩码类型
*/
private String markType;

/**
* 描述
*/
private String desc;


public String getMarkType() {
return markType;
}

public String getDesc() {
return desc;
}

MarkStrategyEnum(String markType, String desc) {
this.markType = markType;
this.desc = desc;
}

public static MarkStrategyEnum getByMarkType(String markType) {

for (MarkStrategyEnum value : values()) {
if (value.getMarkType().equalsIgnoreCase(markType)) {
return value;
}
}

return null;

}

}

脱敏工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class MarkUtils {

private static final Logger logger = LoggerFactory.getLogger(MarkUtils.class);

/**
* 脱敏
*
* @param t
* @param <T>
* @return
*/
public static <T> T mark(T t) {

if (null == t) {
logger.info("脱敏入参为空,返回原始值,不影响主业务");
return t;
}

Class<?> aClass = t.getClass();
Field[] fields = aClass.getDeclaredFields();
if (null == fields || fields.length < 1) {
logger.info("待脱敏的实例对象中无属性值,返回原始值,不影响主业务");
return null;
}

for (Field field : fields) {

// 过滤掉没有脱敏枚举的Field
boolean present = field.isAnnotationPresent(Mark.class);
if (!present) {
continue;
}

Mark annotation = field.getAnnotation(Mark.class);

// 只有开关为true才会实现脱敏操作
if (!annotation.on()) {
continue;
}

String markType = annotation.markType().getMarkType();
if (null == markType || markType.length() < 1) {
logger.warn("脱敏注解属性值markType填写异常,不做脱敏处理|Field:{}", markType);
continue;
}

MarkStrategyEnum strategyEnum = MarkStrategyEnum.getByMarkType(markType);
if (null == strategyEnum) {
logger.info("当前不支持的脱敏类型,不处理|Param:{}", markType);
continue;
}
field.setAccessible(true);
try {
Object value = field.get(t);
if (null == value) {
logger.info("当前Field的属性值为空,不做脱敏处理|Field:{}", field.getName());
continue;
}
String mark = strategyEnum.mark(value.toString());
field.set(t, mark);
} catch (IllegalAccessException e) {
logger.error("脱敏异常,不处理|Field:{}|e:", field.getName(), e);
}
}

return t;
}
}