Spring MVC 大戰 Emoji 表情

一、前言

由于自己在維護一個 APP 的后端項目,于是乎就有了這個坑(數據庫不支持 Emoji 表情,需要額外設置),目前的解決思路是把 Emoji 轉化成可以存儲到數據庫的字符。

科普一下

  • MySQL 的 UTF-8 編碼的一個字符最多 3 個字節,但是一個 Emoji 表情為 4 個字節,所以 UTF-8 不支持存儲 Emoji 表情。但是 UTF-8 的超集utf8mb4 一個字符最多能有 4 字節,所以能支持 Emoji 表情的存儲。

二、版本1:Emoji 轉化成 UTF-8 編碼

/**
 * 對 emoji 表情編碼轉換的工具類
 *
 * @author ybin
 * @since 2017-02-19
 */
public class EmojiUtils {

    private static final Logger LOG = LoggerFactory.getLogger(EmojiUtils.class);

    /**
     * 編碼格式
     */
    private static final String ENCODING = "UTF-8";

    private EmojiUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * 將字符串中的emoji表情轉換成可以在utf-8字符集數據庫中保存的格式(表情占4個字節,需要utf8mb4字符集)
     *
     * @param str 待轉換字符串
     * @return 轉換后字符串
     */
    public static String emojiConvert(String str) {

        if (str == null) return "";

        String patternString = "([\\x{10000}-\\x{10ffff}\ud800-\udfff])";

        Pattern pattern = Pattern.compile(patternString);
        Matcher matcher = pattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            try {
                matcher.appendReplacement(sb, "[[" + URLEncoder.encode(matcher.group(1), ENCODING) + "]]");
            } catch (UnsupportedEncodingException e) {
                LOG.error("emoji convert fail", e);
                return str;
            }
        }
        matcher.appendTail(sb);

        return sb.toString();
    }

    /**
     * 還原utf8數據庫中保存的含轉換后emoji表情的字符串
     *
     * @param str 轉換后的字符串
     * @return 轉換前的字符串
     */
    public static String emojiRecovery(String str) {

        String patternString = "\\[\\[(.*?)\\]\\]";

        Pattern pattern = Pattern.compile(patternString);
        Matcher matcher = pattern.matcher(str);

        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            try {
                matcher.appendReplacement(sb, URLDecoder.decode(matcher.group(1), ENCODING));
            } catch (UnsupportedEncodingException e) {
                LOG.error("emoji recovery fail", e);
                return str;
            }
        }
        matcher.appendTail(sb);

        return sb.toString();
    }

}

?????? 經處理后:

[[%F0%9F%98%9A]][[%F0%9F%98%9C]][[%F0%9F%98%9F]]

三、版本2:Emoji 轉化成字符 :smile:

使用大神的 emoji-java 這個庫可以在代碼段解決這個問題,解決思路:

  • 頁面有一個表情??,在經過處理之后可以是??,將這個字符存入數據庫
  • 讀取的時候可以將??這個字符轉為??

例如: ?? 我可以存儲為:smile:,??存儲為:cry:,等等,可以這樣映射起來。映射規則 emojis.json

3.1 編寫工具類

/**
 * 對 emoji 表情編碼轉換的工具類
 *
 * @author ybin
 * @since 2017-04-14
 */
public class EmojiUtils {

    private static final Logger logger = LoggerFactory.getLogger(EmojiUtils.class);

    private EmojiUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * Replaces the emoji's unicode occurrences by one of their alias
     * (between 2 ':').
     *
     * @param input the string to parse
     *
     * @return the string with the emojis replaced by their alias.
     */
    public static String toAliases(String input) {
        return EmojiParser.parseToAliases(input);
    }

    /**
     * Replaces the emoji's aliases (between 2 ':') occurrences and the html
     * representations by their unicode.
     *
     * @param input the string to parse
     *
     * @return the string with the emojis replaced by their alias.
     */
    public static String toUnicode(String input) {
        return EmojiParser.parseToUnicode(input);
    }

}

1、引入 Maven 依賴

<dependency>
    <groupId>com.vdurmont</groupId>
    <artifactId>emoji-java</artifactId>
    <version>3.2.0</version>
</dependency>

2、常用 API 使用

  • EmojiParser.parseToAliases(string); 將表情符號轉為字符
  • EmojiParser.parseToUnicode(string); 將字符轉為表情符號

工具都好了,接下來就是和 Spring MVC 談一下合作了

四、接入 Spring MVC

目前有了工具還不夠,怎么用呢?每個地方都調用一次嗎?APP 請求了后端在 Controller 中調用 EmojiUtils.toUnicode(string)?然后在調用查詢業務時再調用 EmojiUtils.toAliases(string)?這樣感覺好大的代碼量啊,瞬間想回家種田了。夢醒了生活還是要繼續的,到底能不能在某個地方統一處理一下啊?可以的,Spring MVC 中給我們提供了一系列的方案。

  • 數據在服務器玩了一圈,而我們要做的是在它 進門的時候將表情符號轉為字符,而在它要離開時 返回數據將字符轉為表情符號
  • 入口:OncePerRequestFilter過濾器
  • 出口:HttpMessageConverter消息轉換器

4.1 自定義過濾器

這里統一處理提交的數據,如果有 Emoji 就進行轉換,下一步才交給 Controller 處理。

/**
 * Emoji's 編碼過濾器
 *
 * @author ybin
 * @since 2017-04-14
 */
public class EmojiEncodingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        request = new HttpServletRequestWrapper(request) {

            @Override
            public String getParameter(String name) {
                // 參數名
                // 返回值之前 先進行 Emoji 轉化
                return EmojiUtils.toAliases(super.getParameter(name));
            }

            @Override
            public String[] getParameterValues(String name) {
                // 參數值
                // 返回值之前 先進行 Emoji 轉化
                String[] values = super.getParameterValues(name);
                if (values != null) {
                    for (int i = 0; i < values.length; i++) {
                        values[i] = EmojiUtils.toAliases(values[i]);
                    }
                }
                return values;
            }

        };

        filterChain.doFilter(request, response);
    }

}

4.1.1 配置過濾器

web.xml

<!-- Emoji 編碼過濾器 -->
<filter>
    <filter-name>emojiEncodingFilter</filter-name>
    <filter-class>org.spring.web.filter.EmojiEncodingFilter</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>emojiEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

4.2 自定義消息轉換器

Spring 給我們提供了很多處理不同類型數據的轉換器,思路來自 使用自定義HttpMessageConverter對返回內容進行加密

貌似是從 4.1 起才引入的GsonHttpMessageConverter,而剛好這個項目也是使用 Gson,又省了一步,現在我們要做的如下

/**
 * @author ybin
 * @since 2017-04-14
 */
public class JSONHttpMessageConverter extends GsonHttpMessageConverter {

    public JSONHttpMessageConverter() {
        super.setGson(GsonBuilderUtils.create());
    }

    @Override
    protected void writeInternal(Object o, Type type, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {

        super.writeInternal(o, type, outputMessage);
    }
}

主要是要提供一個按我們風格來的 Gson 實例super.setGson(GsonBuilderUtils.create())(果然是一流的大公司,提供一個可定制性這么高的 JSON 框架 ??),只有你想不到沒有你定制不了的,哈哈

/**
 * 構建自定義 Gson 的工具類
 *
 * @author ybin
 * @since 2017-02-19
 */
public class GsonBuilderUtils {

    public static Gson create() {

        GsonBuilder gb = new GsonBuilder();
        gb.registerTypeAdapter(Date.class, new DateSerializer()).setDateFormat(DateFormat.LONG);
        gb.registerTypeAdapter(Date.class, new DateDeserializer()).setDateFormat(DateFormat.LONG);

        gb.registerTypeAdapter(String.class, new EmojiSerializer());
        gb.registerTypeAdapter(String.class, new EmojiDeserializer());

        gb = gb
                //.serializeNulls()// 序列化null
                //.setDateFormat("yyyy-MM-dd HH:mm:ss")// 設置日期時間格式
        //.setPrettyPrinting()// 格式化輸出 json 數據
        ;

        Gson gson = gb.create();

        return gson;
    }

}

/* *********************** 把時間轉成最原始的Long型. Gson默認的是不支持的, 需要手動處理一下. *********************** */

class DateSerializer implements JsonSerializer<Date> {

    public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src.getTime());
    }

}

class DateDeserializer implements JsonDeserializer<Date> {

    public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {

        return new Date(json.getAsJsonPrimitive().getAsLong());
    }

}

/* ********************************************* Emoji 表情轉化. ********************************************* */

class EmojiSerializer implements JsonSerializer<String> {

    public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(EmojiUtils.toUnicode(src));
    }

}

class EmojiDeserializer implements JsonDeserializer<String> {

    public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {

        return EmojiUtils.toAliases(json.getAsString());
    }

}

這個使用 GsonBuilder 定制的 Gson 主要是將 Date 轉換成 Long 類型,對字符串中的已被轉換的 Emoji 表情進行 將字符轉為表情符號

4.2.1 配置消息轉換器

spring-mvc.xml

<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- 輸出對象轉 JSON 支持 -->
        <bean id="jsonHttpMessageConverter" class="org.spring.http.converter.json.JSONHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>text/json;charset=UTF-8</value>
                    <value>application/json;charset=UTF-8</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

五、檢驗效果

至此,我們在入口和出口都統一安排了,接下來就讓我們來檢驗一下吧

Controller
Postman
log

可見 Emoji 已經被轉換成字符了,這時還沒有返回數據,只是在方法里打印了,可以看到這時日期還是默認的方式,還沒被轉成 long 類型,下一步讓我們來見證奇跡的發生吧

json

成功了,數據到我們項目中玩了一圈出來后完好無損(但在這途中,可是讓我們下了藥的??,動了手腳,它們卻混然不知,嘿嘿嘿),日期類型也按我們的要求轉化成了 long 類型,這樣定義好后,除了在業務的特殊處理的要求,基本上我們就不用管了,一位到位,而不是每個地方都要復制一邊代碼,哈哈。

參考文獻:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,970評論 6 342
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風情閱讀 1,731評論 0 3
  • 我今天抽了一支煙。煙是一個男孩離開的那天給我的,他抽很多煙。我醞釀好多天,今天終于得以實施。我沒有打火機,...
    Scarlett閱讀 509評論 1 1