Spring MVC 大戰(zhàn) Emoji 表情

一、前言

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

科普一下

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

二、版本1:Emoji 轉(zhuǎn)化成 UTF-8 編碼

/**
 * 對(duì) emoji 表情編碼轉(zhuǎn)換的工具類
 *
 * @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表情轉(zhuǎn)換成可以在utf-8字符集數(shù)據(jù)庫中保存的格式(表情占4個(gè)字節(jié),需要utf8mb4字符集)
     *
     * @param str 待轉(zhuǎn)換字符串
     * @return 轉(zhuǎn)換后字符串
     */
    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數(shù)據(jù)庫中保存的含轉(zhuǎn)換后emoji表情的字符串
     *
     * @param str 轉(zhuǎn)換后的字符串
     * @return 轉(zhuǎn)換前的字符串
     */
    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();
    }

}

?????? 經(jīng)處理后:

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

三、版本2:Emoji 轉(zhuǎn)化成字符 :smile:

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

  • 頁面有一個(gè)表情??,在經(jīng)過處理之后可以是??,將這個(gè)字符存入數(shù)據(jù)庫
  • 讀取的時(shí)候可以將??這個(gè)字符轉(zhuǎn)為??

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

3.1 編寫工具類

/**
 * 對(duì) emoji 表情編碼轉(zhuǎn)換的工具類
 *
 * @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); 將表情符號(hào)轉(zhuǎn)為字符
  • EmojiParser.parseToUnicode(string); 將字符轉(zhuǎn)為表情符號(hào)

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

四、接入 Spring MVC

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

  • 數(shù)據(jù)在服務(wù)器玩了一圈,而我們要做的是在它 進(jìn)門的時(shí)候將表情符號(hào)轉(zhuǎn)為字符,而在它要離開時(shí) 返回?cái)?shù)據(jù)將字符轉(zhuǎn)為表情符號(hào)
  • 入口:OncePerRequestFilter過濾器
  • 出口:HttpMessageConverter消息轉(zhuǎn)換器

4.1 自定義過濾器

這里統(tǒng)一處理提交的數(shù)據(jù),如果有 Emoji 就進(jìn)行轉(zhuǎn)換,下一步才交給 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) {
                // 參數(shù)名
                // 返回值之前 先進(jìn)行 Emoji 轉(zhuǎn)化
                return EmojiUtils.toAliases(super.getParameter(name));
            }

            @Override
            public String[] getParameterValues(String name) {
                // 參數(shù)值
                // 返回值之前 先進(jìn)行 Emoji 轉(zhuǎn)化
                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 自定義消息轉(zhuǎn)換器

Spring 給我們提供了很多處理不同類型數(shù)據(jù)的轉(zhuǎn)換器,思路來自 使用自定義HttpMessageConverter對(duì)返回內(nèi)容進(jìn)行加密

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

/**
 * @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);
    }
}

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

/**
 * 構(gòu)建自定義 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")// 設(shè)置日期時(shí)間格式
        //.setPrettyPrinting()// 格式化輸出 json 數(shù)據(jù)
        ;

        Gson gson = gb.create();

        return gson;
    }

}

/* *********************** 把時(shí)間轉(zhuǎn)成最原始的Long型. Gson默認(rèn)的是不支持的, 需要手動(dòng)處理一下. *********************** */

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 表情轉(zhuǎn)化. ********************************************* */

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());
    }

}

這個(gè)使用 GsonBuilder 定制的 Gson 主要是將 Date 轉(zhuǎn)換成 Long 類型,對(duì)字符串中的已被轉(zhuǎn)換的 Emoji 表情進(jìn)行 將字符轉(zhuǎn)為表情符號(hào)。

4.2.1 配置消息轉(zhuǎn)換器

spring-mvc.xml

<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- 輸出對(duì)象轉(zhuǎn) 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>

五、檢驗(yàn)效果

至此,我們?cè)谌肟诤统隹诙冀y(tǒng)一安排了,接下來就讓我們來檢驗(yàn)一下吧

Controller
Postman
log

可見 Emoji 已經(jīng)被轉(zhuǎn)換成字符了,這時(shí)還沒有返回?cái)?shù)據(jù),只是在方法里打印了,可以看到這時(shí)日期還是默認(rèn)的方式,還沒被轉(zhuǎn)成 long 類型,下一步讓我們來見證奇跡的發(fā)生吧

json

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

參考文獻(xiàn):

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,285評(píng)論 6 342
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,823評(píng)論 18 399
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風(fēng)情閱讀 1,861評(píng)論 0 3
  • 我今天抽了一支煙。煙是一個(gè)男孩離開的那天給我的,他抽很多煙。我醞釀好多天,今天終于得以實(shí)施。我沒有打火機(jī),...
    Scarlett閱讀 539評(píng)論 1 1

友情鏈接更多精彩內(nèi)容