本文分享在 Springboot 中解决 Emoji 存储的几个办法。其中常见方式为很多人互相借鉴参考的方案,其他方式为博主脑抽后给出的诡异方案,其中 ObjectMapper方案主要参考Springmvc请求参数的优雅处方式

另外,另一篇博客 中的两幅图对这个问题有一定帮助

常见方式

以下两种为通过搜索可以很容易找到的解决办法

  1. 直接修改数据库配置,通过修改字符集编码支持 Emoji 编码。
  • 优势:对业务代码没有影响
  • 劣势:开销大
  1. 在过滤器上使用第三方类包提供的转换方法,每一次请求中对请求进行转化
  • 优势:对业务代码影响较小,只需编写过滤器定义过滤规则即可
  • 因为是对请求(响应)全文进行过滤,所以支持对字段名的转化

其他方式

以下方式为另辟蹊径的两种方式。

包装字符串类

  1. 包装一个字符串类,替换 String 存储可能含有 Emoji 的字段
  2. 为其建立转换函数,在存储前转化为 unicode,读取后转回 Emoji。

  • 优势:开销较小,只在需要的字段上进行转化,无关内容一律不管。
  • 劣势:下文给出的实现比较弱智,代码耦合度较大,程序中需要用到该字段的地方都会受到影响,暂未想到针对这种办法的优化。
Java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 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
/********** 字符串类 (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 转化的过程中有效

实现如下:

Java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 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
/******* 配置类,关键部分,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)); } }); } }