一个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("广告代码")
    @EmojiField
    private String codes;
    @ApiModelProperty("模板内容")
    @EmojiField
    private 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);
    }

}

单元测试:

@Test
    public 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));
    }

运行结果:
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值