一个emoji表情包处理工具类
- 参考
- 业务场景
- 解决方案
- 方案一(mysql字符集)
- 方案二(emoji表情包转码)
尊重他人成果、转载请注明出处
参考
https://github.com/vdurmont/emoji-java
业务场景
在常规如APP推送、BBS、论坛等类型的业务系统中,消息内容中包含emoji表情包(😁、💣、🦢)由于其本身的趣味性,在消息内容中引入的场景越来越普遍。
但是我们发现,若果直接存储emoji表情包,数据库通常会直接报如下异常:
Caused by: java.sql.SQLException: Incorrect string value: ‘\xF6\x8D\x78\x84’ for column ‘comment’ at row 1
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1074)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4096)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4028)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2490)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2651)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2734)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2155)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2458)
这是因为本身并不是直接支持emoji表情包,需要通过配置字符编码方式或者其它方案才能实现emoji包存储。
目前业界有两种解决方案:一、配置mysql字符集成utf8mb4;二、对emoji表情包进行转码存储。
两种方案各有优势吧,utf8mb4方案对数据库版本有要求,仅需修改配置,无需修改代码。从根源上解决emoji表情包存储问题。emoji表情包转码方案则对数据库版本没啥要求,但是需要对表情包内容进行编码、解码操作,因此对代码侵入性高。
这两种方案根据业务场景取舍吧。
解决方案
方案一(mysql字符集)
mysql5.5版本之前,数据库中的utf-8编码只支持1~3个字节(注:数据库中的utf-8并非广义上讲的utf-8,仅是utf-8的精简版本)。从mysql5.5开始支持4个字节的utf8mb4。
emoji表情包占用4个字节,因此要求mysql支持emoji表情包,则需要配置utf8mb4编码方式。目前网上有相当丰富的资源介绍,因此本文仅简单介绍。
查看mysql当前的编码方式:
show variables like ‘%character%’;
方案二(emoji表情包转码)
针对已经成熟的线上系统或者mysql版本较老,基于数据安全及系统稳定性考虑,建议采用emoji表情包转码方案,无需修改线上数据库配置,仅对emoji表情包转码存储。
本方案对开源emoji表情包类库进行二次封装、自定义注解EmojiField、工具类EmojiUtil实现,emoji表情包快速转码处理。
工具类包结构如下图:
包括一个注解类@EmojiField、一个工具类EmojiUtil。EmojiUtil对emoji工具类库( https://github.com/vdurmont/emoji-java)进行二次封装,利用java反射特性对POJO对象中的某个string字段进行emoji表情包转码。
@EmojiFiled:
/*** @description: 指定指端做emoji字符转换* @author: luoping* @date: 2022/10/21**/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EmojiField {}
POJO对象:
@ApiModel("资源基本信息")
@Data
public class TResourceBaseInfoVO {@ApiModelProperty("自增id")private Long id;@ApiModelProperty("资源名称")private String name;@ApiModelProperty("广告代码")@EmojiFieldprivate String codes;@ApiModelProperty("模板内容")@EmojiFieldprivate String templateContent;
}
EmojiUtil:
package xxx.xxx;import com.alibaba.fastjson.JSONObject;
import com.vdurmont.emoji.EmojiManager;
import com.vdurmont.emoji.EmojiParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.util.StringUtil;import java.lang.reflect.Field;/*** @description: 表情包工具类* @author: luoping* @date: 2022/10/21**/
@Slf4j
public class EmojiUtil {/*** 将文本中的emoji表情包转码* @param emojiStr* @return*/public static String encode(String emojiStr) {if(StringUtil.isBlank(emojiStr)) {return null;}if(!EmojiManager.containsEmoji(emojiStr)) {return emojiStr;}return EmojiParser.parseToAliases(emojiStr);}/*** 将文本中的emoji表情(转码后)恢复成表情包* @param deEmojiStr* @return*/public static String decode(String deEmojiStr) {if(StringUtil.isBlank(deEmojiStr)) {return null;}
// if(!EmojiManager.containsEmoji(deEmojiStr)) {
// return deEmojiStr;
// }return EmojiParser.parseToUnicode(deEmojiStr);}/*** 将对象中@EmojiField修饰的字符串进行emoji表情包转码* @param t* @param <T>*/public static <T> void encode(T t) {Field[] fields = t.getClass().getDeclaredFields();for (Field field : fields) {// 判断字段注解是否存在boolean annotationPresent = field.isAnnotationPresent(EmojiField.class);if (annotationPresent) {if(!StringUtils.equals(field.getType().getName(), "java.lang.String")) {// 只处理String类型continue;}// 将字段中包含emoji表情包的内容进行转码赋值field.setAccessible(true);try {String srcStr = (String)field.get(t);field.set(t, encode(srcStr));}catch (Exception e) {log.error("EmojiUtil encode error: {}", JSONObject.toJSONString(t), e);}field.setAccessible(false);}}}/*** 将对象中@EmojiField修饰的字符串进行emoji表情包解码* @param t* @param <T>*/public static <T> void decode(T t) {Field[] fields = t.getClass().getDeclaredFields();for (Field field : fields) {// 判断字段注解是否存在boolean annotationPresent = field.isAnnotationPresent(EmojiField.class);if (annotationPresent) {if(!StringUtils.equals(field.getType().getName(), "java.lang.String")) {// 只处理String类型continue;}// 将字段中包含emoji表情包的内容进行转码赋值field.setAccessible(true);try {String srcStr = (String)field.get(t);field.set(t, decode(srcStr));}catch (Exception e) {log.error("EmojiUtil decode error: {}", JSONObject.toJSONString(t), e);}field.setAccessible(false);}}}/*** 是否包含emoji表情包* @param str* @return*/public static boolean containEmoji(String str) {return EmojiManager.containsEmoji(str);}}
单元测试:
@Testpublic void testEmojiUtil() {TResourceBaseInfoAddReqVO vo = new TResourceBaseInfoAddReqVO();vo.setId(1000l);vo.setCodes("哈喽emoji😁");vo.setTemplateContent("😁💣");System.out.println("编码前:");System.out.println(JSONObject.toJSONString(vo));EmojiUtil.encode(vo);System.out.println("编码后:");System.out.println(JSONObject.toJSONString(vo));EmojiUtil.decode(vo);System.out.println("解码后:");System.out.println(JSONObject.toJSONString(vo));}
运行结果: