本文分享在 Springboot 中解决 Emoji 存储的几个办法。其中常见方式为很多人互相借鉴参考的方案,其他方式为博主脑抽后给出的诡异方案,其中 ObjectMapper方案主要参考Springmvc请求参数的优雅处方式
另外,另一篇博客 中的两幅图对这个问题有一定帮助
常见方式
以下两种为通过搜索可以很容易找到的解决办法
- 直接修改数据库配置,通过修改字符集编码支持 Emoji 编码。
- 优势:对业务代码没有影响
- 劣势:开销大
- 在过滤器上使用第三方类包提供的转换方法,每一次请求中对请求进行转化
- 优势:对业务代码影响较小,只需编写过滤器定义过滤规则即可
- 因为是对请求(响应)全文进行过滤,所以支持对字段名的转化
其他方式
以下方式为另辟蹊径的两种方式。
包装字符串类
- 包装一个字符串类,替换 String 存储可能含有 Emoji 的字段
- 为其建立转换函数,在存储前转化为 unicode,读取后转回 Emoji。
- 优势:开销较小,只在需要的字段上进行转化,无关内容一律不管。
- 劣势:下文给出的实现比较弱智,代码耦合度较大,程序中需要用到该字段的地方都会受到影响,暂未想到针对这种办法的优化。
/********** 字符串类 (EmojiSupportedString.java) ***********/
import lombok.AllArgsConstructor;
import lombok.Data;
// 支持 Emoji 的字符串,String 不可继承,通过包装类实现
@Data
@AllArgsConstructor
public class EmojiSupportedString {
/**
* 可能包含 Emoji 等四字节字符的字符串
*/
private String value;
@Override
public String toString() {
return value;
}
}
/********** 转化器类 (EmojiConverter.java) ***********/
import cn.hutool.extra.emoji.EmojiUtil;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter(autoApply = true)
public class EmojiConverter implements AttributeConverter<EmojiSupportedString, String> {
@Override
public String convertToDatabaseColumn(EmojiSupportedString attribute) {
// 使用 Hutool 实现转化
return EmojiUtil.toAlias(attribute.getValue());
}
@Override
public EmojiSupportedString convertToEntityAttribute(String dbData) {
return new EmojiSupportedString(EmojiUtil.toUnicode(dbData));
}
}
/************** 实体类举例 **********/
@Entity
@Data
@Table(name="comment")
public class Comment {
@Column(name = "content")
EmojiSupportedString content;
}
自定义 ObjectMapper 实现转化
折腾了一段时间没有得到想要的实现,准备继续使用 Filter 方案时,一篇文章让我有了另一种思路,即使用 ObjectMapper 针对请求体 Json 实现转化。
但是下文的实现不支持对参数中字符串的转化,对非接口式(如直接返回页面)的请求也不支持 那TM还支持啥 适合用在提供纯接口服务的后端程序。
如果需要,也可以通过扩展 WebBindingInitializer 的方式实现针对参数的支持。(下文思路基于文章开头给出的参考实现,如有需要,可以参考)
- 优势:只针对请求中的字段值进行转化,开销较全文转化更小
- 优势:需要将自定义 ObjectMapper 配置到 Spring 中,其他业务代码中,无需关心 Emoji
- 劣势:支持的范围受限,仅在发生 Json 转化的过程中有效
实现如下:
/******* 配置类,关键部分,WebMvcAutoConfiguration.java *********/
@Configuration
public class WebMvcAutoConfiguration extends WebMvcConfigurationSupport {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.findFirst()
.ifPresent(converter -> {
// 添加自定义序列化模块,使用类似的思想可以定义其他行为,比如自定义分页字段等
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) converter;
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
JsonComponentModule module = new JsonComponentModule();
module.addSerializer(new PageJacksonSerializer()); // 定制分页的 json 序列化行为
ObjectMapper objectMapper = builder.modules(module, new EmojiSupportSimpleModule()).build();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
});
}
}
/******* 自定义 ObjectMapper 转化模块 ***************/
import cn.hutool.extra.emoji.EmojiUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
public class EmojiSupportSimpleModule extends SimpleModule {
{
// Emoji 转化为别名
addDeserializer(String.class, new StdDeserializer<>(String.class) {
@Override
public String deserialize(JsonParser p, DeserializationContext
ctxt) throws IOException, JsonProcessingException {
return EmojiUtil.toAlias(p.getValueAsString());
}
});
// 别名转回 Emoji
addSerializer(String.class, new StdSerializer<>(String.class) {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(EmojiUtil.toUnicode(value));
}
});
}
}