1. 背景
一般前后端使用的是Json格式的报文来进行交互,而SpringBoot默认采用Jackson来帮助我们完成协议报文序列化和反序列化。
而有时我们需要对协议报文一些个性化处理,例如:
- 敏感信息(手机号,身份证等)脱敏;
- 参数的加密和解密;
- 文件防盗链设置(文件上传接口,上传返回临时地址;业务接口中,将临时链接转化成永久链接存储到DB)
这些工作都可以在业务代码中来进行编写,但是会调用非常多的冗余代码。可以通过自定义Jackson注解,在Json序列化成对象,对象反序列化成Json时,来进行动态处理
2. 序列化-对象转Json串处理
例子:手机号脱敏处理
定义注解:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@Target(ElementType.FIELD)
@JsonSerialize(using = SensitiveDataSerializer.class)
public @interface SensitiveData {
/**
* 默认的字段脱敏替换字符串
*/
String DEFAULT_REPLACE_STRING = "*";
/**
* 脱敏策略
*/
Strategy strategy() default Strategy.TOTAL;
/**
* 脱敏长度,在Strategy.TOTAL策略下忽略该字段
*/
int length() default 0;
/**
* 脱敏字段替换字符
*/
String replaceStr() default DEFAULT_REPLACE_STRING;
enum Strategy {
/**
* 全部
*/
TOTAL,
/**
* 从左边开始
*/
LEFT,
/**
* 从右边开始
*/
RIGHT
}
}
定义反序列化解析类:
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
/**
* 脱敏字段序列化器
*/
public class SensitiveDataSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveData sensitiveData;
public SensitiveDataSerializer(SensitiveData sensitiveData) {
this.sensitiveData = sensitiveData;
}
public SensitiveDataSerializer() {
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (StringUtils.isBlank(value)) {
gen.writeString(value);
return;
}
if (sensitiveData != null) {
final SensitiveData.Strategy strategy = sensitiveData.strategy();
final int length = sensitiveData.length();
final String replaceString = sensitiveData.replaceStr();
gen.writeString(getValue(value, strategy, length, replaceString));
} else {
gen.writeString(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
SensitiveData annotation = property.getAnnotation(SensitiveData.class);
if (annotation != null) {
return new SensitiveDataSerializer(annotation);
}
return this;
}
private String getValue(String rawStr, SensitiveData.Strategy strategy, int length, String replaceString) {
switch (strategy) {
case TOTAL:
return rawStr.replaceAll("[\\s\\S]", replaceString);
case LEFT:
return replaceByLength(rawStr, length, replaceString, true);
case RIGHT:
return replaceByLength(rawStr, length, replaceString, false);
default:
throw new IllegalArgumentException("Illegal Sensitive Strategy");
}
}
private String replaceByLength(String rawStr, int length, String replaceString, boolean fromLeft) {
if (StringUtils.isBlank(rawStr)) {
return rawStr;
}
if (rawStr.length() <= length) {
return rawStr.replaceAll("[\\s\\S]", replaceString);
}
if (fromLeft) {
return getSpecStringSequence(length, replaceString) + rawStr.substring(length);
} else {
return rawStr.substring(0, length) + getSpecStringSequence(length, replaceString);
}
}
private String getSpecStringSequence(int length, String str) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < length; i++) {
stringBuilder.append(str);
}
return stringBuilder.toString();
}
}
3. 反序列化-Json转对象
加密字段解密
定义注解:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@Target(ElementType.FIELD)
@JsonDeserialize(using = HdxDecryptDeserialize.class)
public @interface HdxDecrypt {
String strategy() default "default";
}
定义反序列化处理器:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HdxDecryptDeserialize extends JsonDeserializer<String> implements ContextualDeserializer {
private HdxDecrypt hdxDecrypt;
public HdxDecryptDeserialize() {
}
public HdxDecryptDeserialize(HdxDecrypt hdxDecrypt) {
this.hdxDecrypt = hdxDecrypt;
}
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String encryptedKey = p.getText().trim();
return "已经解密,解密策略:" + hdxDecrypt.strategy() + " " + encryptedKey;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
HdxDecrypt annotation = property.getAnnotation(HdxDecrypt.class);
if (annotation != null) {
return new HdxDecryptDeserialize(annotation);
}
return this;
}
}
4. 使用方式
定义对象:
import java.time.LocalDate;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Person {
private Long id;
private String name;
private String address;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createAt;
private LocalDateTime updateAt;
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate lastLoginDate;
@SensitiveData(strategy = SensitiveData.Strategy.LEFT, length = 6, replaceStr = "*")
private String mobile;
/**
* 对象打平输出
*/
@JsonUnwrapped
private Role role;
@HdxDecrypt(strategy = "custom")
private String key;
@Data
public static class Role {
private long id;
private String roleName;
}
}
定义Controller层
@RestController
@RequestMapping("/person")
@Slf4j
public class PersonController {
@GetMapping("/detail")
public Person detail() {
Role role = new Role();
role.setId(12L);
role.setRoleName("管理员");
return Person.builder()
.id(12L)
.age(18)
.name("lis")
.address("北京")
.createAt(LocalDateTime.now())
.updateAt(LocalDateTime.now())
.lastLoginDate(LocalDate.now())
.mobile("15824984456")
.role(role)
.build();
}
@PostMapping("/save")
public Person save(@RequestBody Person person) {
log.info("用户信息: {}", person);
return person;
}
}
查看结果:
查看结果.png