美文网首页java技术程序员Spring Boot
Spring MVC 大战 Emoji 表情

Spring MVC 大战 Emoji 表情

作者: y0ngb1n | 来源:发表于2017-04-15 01:02 被阅读1096次

    一、前言

    由于自己在维护一个 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 处理。
    
    ```java
    /**
     * 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 MVC 大战 Emoji 表情

      本文链接:https://www.haomeiwen.com/subject/psvgattx.html