zoukankan      html  css  js  c++  java
  • 使用过滤器实现后台返回Response国际化

    前言 :
            写这篇文档之前,其实我看过了spring的国际化处理,使用spring去处理国际化也确实方便,但由于公司项目是已经做好了的,只有一个中文版,如果直接改成用spring的话,需要改的代码量非常大,所以我就想着根据自己的项目,然后模仿spring的做法去实现国际化。

    一、前端

            现在我们做的项目是前后端分离的,也就是后端的请求全部返回JSON数据,前端全部使用Ajax去处理后台返回的数据,所以前端页面的国际化实现,实际上就是copy一份中文版的项目,然后把页面上的中文全部改成英文的,这种做法实在是最蠢的,如果以后需要添加更多的语言支持,岂不是要copy好几份出来,所以这种做法是不可取的,虽然我们是用的这种做法。更好的方法是前端也使用国际化框架,这里比较常用的就是 jquery.i18n.properties的使用讲解与实例 这个了,有兴趣的可以看看,本文主要讲的是后台的实现方法。

    二、后台

    1、思路

            我们模仿spring的做法,定义两套properties文件,一个中文一个英文,然后通过前端传过来的请求头language去判断是中文版(zh-cn)的前端访问还是英文版(en-us)的前端在访问, 后台使用一个过滤器过滤response,实际上就是动态去加载properties中的value去替换response中的返回值。

    2、返回值

    后台的返回值是用自定义的一个对象封装起来的,仅作参考。 代码如下:

    package com.blog.www.bean.common;
    
    import lombok.*;
    import org.springframework.context.annotation.Scope;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    
    
    
    
    /**
     * 响应对象。包含处理结果(Meta)和返回数据(Data)两部分,在 Controller 处理完请求后将此对象转换成 json 返回给前台。注意:
     * <ul>
     * <li>处理成功一般返回处理结果和返回数据,失败只返回处理结果。具体返回什么需看接口文档。</li>
     * <li>处理成功结果码一般是200,失败码具体看出了什么错,对照 HTTP 响应码填。</li>
     * <li>默认处理方法慎用,前台最想要拿到的还是具体的结果码和信息。</li>
     * </ul>
     * <p>
     * @author :leigq <br>
     * 创建时间:2017年10月9日 下午3:26:17 <br>
     * <p>
     * 修改人: <br>
     * 修改时间: <br>
     * 修改备注: <br>
     * </p>
     */
    @Component
    @Scope("prototype")
    @SuppressWarnings(value = "all")
    @AllArgsConstructor
    @NoArgsConstructor
    public class Response {
    
    	/**
    	 * 默认成功响应码
    	 */
    	private static final Integer DEFAULT_SUCCESS_CODE = HttpStatus.OK.value();
    
    	/**
    	 * 默认成功响应信息
    	 */
    	private static final String DEFAULT_SUCCESS_MSG = "请求/处理成功!";
    
    	/**
    	 * 默认国际化成功响应信息
    	 */
    	private static final String DEFAULT_I18N_SUCCESS_MSG = "REQUEST_SUCCESS";
    
    	/**
    	 * 默认失败响应码
    	 */
    	private static final Integer DEFAULT_FAILURE_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value();
    
    	/**
    	 * 默认失败响应信息
    	 */
    	private static final String DEFAULT_FAILURE_MSG = "请求/处理失败!";
    
    	/**
    	 * 默认国际化失败响应信息
    	 */
    	private static final String DEFAULT_I18N_FAILURE_MSG = "REQUEST_FAIL";
    
    	@Getter
    	@Setter
    	private Meta meta;
    
    	@Getter
    	@Setter
    	private Object data;
    
    	/*******处理成功响应*************************************************************************************/
    
    	/*↓↓↓↓↓↓默认(200)响应码,默认信息,无返回数据↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    	/**
    	 * 处理成功响应,默认(200)响应码,默认信息,无返回数据
    	 *
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response success() {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MSG, false, null);
    		this.data = null;
    		return this;
    	}
    
    	/**
    	 * 处理国际化成功响应,默认(200)响应码,默认信息,无返回数据
    	 *
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response successI18n() {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, DEFAULT_I18N_SUCCESS_MSG, true, null);
    		this.data = null;
    		return this;
    	}
    
    
    	/*↓↓↓↓↓↓默认(200)响应码,自定义信息,无返回数据↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    	/**
    	 * 处理成功响应,默认(200)响应码,自定义信息,无返回数据
    	 *
    	 * @param msg  处理结果信息
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response success(String msg) {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, msg, false, null);
    		this.data = null;
    		return this;
    	}
    
    	/**
    	 * 处理成功响应,默认(200)响应码,自定义信息,无返回数据
    	 *
    	 * @param msg  处理结果信息
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response successI18n(String msg) {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, msg, true, null);
    		this.data = null;
    		return this;
    	}
    
    	/**
    	 * 处理成功响应,默认(200)响应码,自定义信息,无返回数据
    	 *
    	 * @param msg  处理结果信息
    	 * @param msgParams 结果信息参数
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response successI18n(String msg, Object[] msgParams) {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, msg, true, msgParams);
    		this.data = null;
    		return this;
    	}
    
    
    	/*↓↓↓↓↓↓默认(200)响应码,默认信息,有返回数据。↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    	/**
    	 * 处理成功响应,默认(200)响应码,默认信息,有返回数据。
    	 *
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response success(Object data) {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MSG, false, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理成功响应,默认(200)响应码,默认信息,有返回数据。
    	 *
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response successI18n(Object data) {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, DEFAULT_I18N_SUCCESS_MSG, true, null);
    		this.data = data;
    		return this;
    	}
    
    
    	/*↓↓↓↓↓↓默认(200)响应码,自定义信息,有返回数据↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    
    	/**
    	 * 处理成功响应,默认(200)响应码,自定义信息,有返回数据
    	 *
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response success(String msg, Object data) {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, msg, false, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理成功响应,默认(200)响应码,自定义信息,有返回数据
    	 *
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response successI18n(String msg, Object data) {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, msg, true, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理成功响应,默认(200)响应码,自定义信息,有返回数据
    	 *
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @param msgParams 结果信息参数
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response successI18n(String msg, Object data, Object[] msgParams) {
    		this.meta = new Meta(DEFAULT_SUCCESS_CODE, msg, true, msgParams);
    		this.data = data;
    		return this;
    	}
    
    
    	/*↓↓↓↓↓↓自定义响应码,自定义信息,有返回数据↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    	/**
    	 * 处理成功响应,自定义响应码,自定义信息,有返回数据
    	 *
    	 * @param httpStatus HTTP 响应码
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response success(HttpStatus httpStatus, String msg, Object data) {
    		this.meta = new Meta(httpStatus.value(), msg, false, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理成功响应,自定义响应码,自定义信息,有返回数据
    	 *
    	 * @param httpStatus HTTP 响应码
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response successI18n(HttpStatus httpStatus, String msg, Object data) {
    		this.meta = new Meta(httpStatus.value(), msg, true, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理成功响应,自定义响应码,自定义信息,有返回数据
    	 *
    	 * @param httpStatus HTTP 响应码
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @param msgParams 结果信息参数
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:25 <br>
    	 */
    	public Response successI18n(HttpStatus httpStatus, String msg, Object data, Object[] msgParams) {
    		this.meta = new Meta(httpStatus.value(), msg, true, msgParams);
    		this.data = data;
    		return this;
    	}
    
    
    	/*******处理失败响应*************************************************************************************/
    
    	/*↓↓↓↓↓↓默认(500)响应码,默认信息,无返回数据↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    	/**
    	 * 处理失败响应,返回默认(500)响应码、默认信息,无返回数据。
    	 *
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failure() {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, DEFAULT_FAILURE_MSG, false, null);
    		this.data = null;
    		return this;
    	}
    
    	/**
    	 * 处理国际化失败响应,返回默认(500)响应码、默认信息,无返回数据。
    	 *
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failureI18n() {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, DEFAULT_I18N_FAILURE_MSG, true, null);
    		this.data = null;
    		return this;
    	}
    
    	/*↓↓↓↓↓↓默认(500)响应码,自定义信息,无返回数据↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    	/**
    	 * 处理失败响应,返回默认(500)响应码、自定义信息,无返回数据。
    	 *
    	 * @param msg 处理结果信息
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failure(String msg) {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, msg, false, null);
    		this.data = null;
    		return this;
    	}
    
    	/**
    	 * 处理国际化失败响应,返回默认(500)响应码、自定义信息,无返回数据。
    	 *
    	 * @param msg 处理结果信息
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failureI18n(String msg) {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, msg, true, null);
    		this.data = null;
    		return this;
    	}
    
    	/**
    	 * 处理国际化失败响应,返回默认(500)响应码、自定义信息,无返回数据。
    	 *
    	 * @param msg 处理结果信息
    	 * @param msgParams 结果信息参数
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failureI18n(String msg, Object[] msgParams) {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, msg, true, msgParams);
    		this.data = null;
    		return this;
    	}
    
    	/*↓↓↓↓↓↓默认(500)响应码,默认信息,有返回数据。↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    	/**
    	 * 处理失败响应,默认(500)响应码,默认信息,有返回数据。
    	 *
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failure(Object data) {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, DEFAULT_FAILURE_MSG, false, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理国际化失败响应,默认(500)响应码,默认信息,有返回数据。
    	 *
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failureI18n(Object data) {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, DEFAULT_I18N_FAILURE_MSG, true, null);
    		this.data = data;
    		return this;
    	}
    
    
    	/*↓↓↓↓↓↓默认(500)响应码,自定义信息,有返回数据↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    
    	/**
    	 * 处理失败响应,默认(500)响应码,自定义信息,有返回数据
    	 *
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failure(String msg, Object data) {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, msg, false, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理国际化失败响应,默认(500)响应码,自定义信息,有返回数据,有结果信息参数。
    	 *
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @param msgParams 结果信息参数
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failureI18n(String msg, Object data, Object[] msgParams) {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, msg, true, msgParams);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理国际化失败响应,默认(500)响应码,自定义信息,有返回数据,无结果信息参数。
    	 *
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failureI18n(String msg, Object data) {
    		this.meta = new Meta(DEFAULT_FAILURE_CODE, msg, true, null);
    		this.data = data;
    		return this;
    	}
    
    
    	/*↓↓↓↓↓↓自定义响应码,自定义信息,有返回数据↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
    
    	/**
    	 * 处理失败响应,自定义响应码,自定义信息,有返回数据
    	 *
    	 * @param httpStatus HTTP 响应码
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failure(HttpStatus httpStatus, String msg, Object data) {
    		this.meta = new Meta(httpStatus.value(), msg, false, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理国际化失败响应,自定义响应码,自定义信息,有返回数据,有结果信息参数。
    	 *
    	 * @param httpStatus HTTP 响应码
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @param msgParams 结果信息参数
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failureI18n(HttpStatus httpStatus, String msg, Object data, Object[] msgParams) {
    		this.meta = new Meta(httpStatus.value(), msg, true, msgParams);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 处理国际化失败响应,自定义响应码,自定义信息,有返回数据,无结果信息参数。
    	 *
    	 * @param httpStatus HTTP 响应码
    	 * @param msg  处理结果信息
    	 * @param data 返回数据
    	 * @return 响应对象
    	 * <p>
    	 * @author :LeiGQ <br>
    	 * @date :2019-05-20 15:22 <br>
    	 */
    	public Response failureI18n(HttpStatus httpStatus, String msg, Object data) {
    		this.meta = new Meta(httpStatus.value(), msg, true, null);
    		this.data = data;
    		return this;
    	}
    
    	/**
    	 * 元数据,包含响应码和信息。
    	 * <p>
    	 * 创建人:leigq <br>
    	 * 创建时间:2017年10月9日 下午3:31:17 <br>
    	 * <p>
    	 * 修改人: <br>
    	 * 修改时间: <br>
    	 * 修改备注: <br>
    	 * </p>
    	 */
    	@Data
    	@AllArgsConstructor
    	@NoArgsConstructor
    	public class Meta {
    
    		/**
    		 * 处理结果代码,与 HTTP 状态响应码对应
    		 */
    		private Integer code;
    
    		/**
    		 * 处理结果信息
    		 */
    		private String msg;
    
    		/**
    		 * 处理结果信息是否国际化
    		 */
    		private Boolean isI18n;
    
    		/**
    		 * 处理结果信息参数
    		 */
    		private Object[] msgParams;
    	}
    
    }
    

    后台调用的时候是这样的:
    在这里插入图片描述
    最后前端显示:

    在这里插入图片描述
    在这里插入图片描述
    还有一种情况是,提示语中的信息可能是动态填充进去的,像下面这样,那么,我们就可以使用 {} 来进行占位, 填充数据使用数组装起来即可。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    3、properties文件

    定义两套properties文件,用来存放国际化的字符串,language_zh_CN.properties[中文]和language_en_US.properties[英文],像下面这样:
    在这里插入图片描述

    这里需要注意的是,俩个properties文件的key要保持一致,以确保能正常取值。

    4、过滤器

    过滤器不会用的看这里:Java过滤器Filter使用详解

    过滤器直接看代码,注释很详细,这里我的项目使用的是SpringBoot,所以就直接用注解定义过滤器了

    package com.blog.www.web;
    
    import com.blog.www.bean.common.Response;
    import com.blog.www.bean.i18n.CnI18nPropertiesStrategy;
    import com.blog.www.bean.i18n.EnI18nPropertiesStrategy;
    import com.blog.www.bean.i18n.I18nPropertiesStrategy;
    import com.blog.www.bean.i18n.I18nPropertiesStrategyContext;
    import com.blog.www.util.JSONUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Objects;
    
    /**
     * 实现国际化过滤器
     * <p>通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。
     * 例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
     * 使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
     * <br/>
     * 具体如何实现国际化,参考:<a href='http://note.youdao.com/noteshare?id=53637753599255d98737bfc4060cb4b7'>使用过滤器实现后台返回Response国际化</a>
     * 创建人:leigq <br>
     * 创建时间:2018-11-05 11:43 <br>
     * <p>
     * 修改人: <br>
     * 修改时间: <br>
     * 修改备注: <br>
     * </p>
     */
    @WebFilter(filterName = "响应国际化过滤器", urlPatterns = "/*")
    // 标注这是一个过滤器,属性filterName声明过滤器的名称(可选);属性urlPatterns指定要过滤的URL模式,也可使用属性value来声明(指定要过滤的URL模式是必选属性)
    @Order(Ordered.HIGHEST_PRECEDENCE)// 控制加载顺序,Ordered.HIGHEST_PRECEDENCE最高优先级
    @Slf4j
    public class I18NFilter implements Filter {
    	@Autowired
    	private Map<String, I18nPropertiesStrategy> i18nPropertiesStrategyMap;
    
    	/**
    	 * 前端国际化请求头 key
    	 */
    	private static final String LANGUAGE_HEADER = "Language";
    
    	/*
    	 * 前端请求头 value
    	 * */
    	// 中文
    	private static final String ZH_CN = "zh-cn";
    	// 英文
    	private static final String EN_US = "en-us";
    
    	/**
    	 * 用于保存请求头中的语言参数
    	 */
    	private String language;
    
    	@Override
    	public void init(FilterConfig filterConfig) {
    		log.warn("国际化过滤器-init...");
    	}
    
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    		log.info(">>>>>>>>>>请求进入国际化过滤器<<<<<<<<<");
    		HttpServletResponse resp = (HttpServletResponse) response;
    		HttpServletRequest req = (HttpServletRequest) request;
    		// 获取请求头中的语言参数
    		language = req.getHeader(LANGUAGE_HEADER);
    		if (StringUtils.isNotBlank(language)) {
    			handleResponse(request, response, resp, chain);
    		} else {
    			log.info(">>>>>> 国际化过滤器不做处理 <<<<<<");
    			try {
    				response.setCharacterEncoding("UTF-8");
    				chain.doFilter(request, response);
    			} catch (Exception e) {
    				log.info("处理国际化返回结果失败", e);
    			}
    		}
    	}
    
    	/**
    	 * 处理响应
    	 */
    	private void handleResponse(ServletRequest request, ServletResponse response, HttpServletResponse resp, FilterChain chain) {
    		// 包装响应对象 resp 并缓存响应数据
    		ResponseWrapper mResp = new ResponseWrapper(resp);
    		ServletOutputStream out = null;
    		try {
    			out = response.getOutputStream();
    			// 防止出现乱码
    			mResp.setCharacterEncoding("UTF-8");
    			chain.doFilter(request, mResp);
    			// 获取缓存的响应数据
    			byte[] bytes = mResp.getBytes();
    			// 响应字符串
    			String responseStr = new String(bytes, "UTF-8");
    			// 将 String 类型响应数据转成 Response 对象
    			Response returnResponse = JSONUtils.json2pojo(responseStr, Response.class);
    			// meta 对象
    			Response.Meta meta = returnResponse.getMeta();
    			// 返回信息
    			String msg = meta.getMsg();
    			if (meta.getIsI18n()) {
    				// 返回信息参数
    				Object[] msgParams = meta.getMsgParams();
    				// 处理国际化
    				if (Objects.isNull(msgParams)) {
    					// 直接用 value 替换 key
    					responseStr = responseStr.replace(msg, getI18nVal(msg));
    				} else {
    					// 循环用 value 替换 key
    					String[] keys = msg.split("\{}");
    					for (String key : keys) {
    						responseStr = responseStr.replaceFirst(key, getI18nVal(key));
    					}
    					// 循环处理返回结果参数
    					for (Object param : msgParams) {
    						responseStr = responseStr.replaceFirst("\{}", param.toString());
    					}
    				}
    			}
    			out.write(responseStr.getBytes());
    		} catch (Exception e) {
    			log.error("处理国际化返回结果失败", e);
    		} finally {
    			try {
    				assert out != null;
    				out.flush();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	/**
    	 * 根据properties文件中属性的key获取对应的值
    	 * 说明:
    	 * <p>
    	 * 创建人: LGQ <br>
    	 * 创建时间: 2018年8月13日 下午8:10:20 <br>
    	 * <p>
    	 * 修改人: <br>
    	 * 修改时间: <br>
    	 * 修改备注: <br>
    	 * </p>
    	 */
    	private String getI18nVal(String langKey) {
    		I18nPropertiesStrategy i18nPropertiesStrategy;
    		switch (language) {
    			case ZH_CN:
    				i18nPropertiesStrategy = i18nPropertiesStrategyMap.get("cnI18nPropertiesStrategy");
    				break;
    			case EN_US:
    				i18nPropertiesStrategy = i18nPropertiesStrategyMap.get("enI18nPropertiesStrategy");
    				break;
    			default:
    				i18nPropertiesStrategy = i18nPropertiesStrategyMap.get("cnI18nPropertiesStrategy");
    				break;
    		}
    		String value = i18nPropertiesStrategy.getValue(langKey);
    		log.info("I18N Filter ### key = {} ---->  value = {}", langKey, value);
    		return value;
    	}
    
    	@Override
    	public void destroy() {
    		log.warn("国际化过滤器-destroy...");
    	}
    }
    

    上面的过滤器中使用到了下面的几个类。

    ResponseWrapper.java

    package com.blog.www.web;
    
    import javax.servlet.ServletOutputStream;
    import javax.servlet.WriteListener;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;
    import java.io.*;
    
    public class ResponseWrapper extends HttpServletResponseWrapper {
    	private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    	private PrintWriter pwrite;
    
    	ResponseWrapper(HttpServletResponse response) {
    		super(response);
    	}
    
    	@Override
    	public ServletOutputStream getOutputStream() {
    		// 将数据写到 byte 中
    		return new MyServletOutputStream(bytes);
    	}
    
    	/**
    	 * 重写父类的 getWriter() 方法,将响应数据缓存在 PrintWriter 中
    	 */
    	@Override
    	public PrintWriter getWriter() {
    		try {
    			pwrite = new PrintWriter(new OutputStreamWriter(bytes, "utf-8"));
    		} catch (UnsupportedEncodingException e) {
    			e.printStackTrace();
    		}
    		return pwrite;
    	}
    
    	/**
    	 * 获取缓存在 PrintWriter 中的响应数据
    	 */
    	byte[] getBytes() {
    		if (null != pwrite) {
    			pwrite.close();
    			return bytes.toByteArray();
    		}
    
    		if (null != bytes) {
    			try {
    				bytes.flush();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		assert bytes != null;
    		return bytes.toByteArray();
    	}
    
    
    	class MyServletOutputStream extends ServletOutputStream {
    		private ByteArrayOutputStream ostream;
    
    		MyServletOutputStream(ByteArrayOutputStream ostream) {
    			this.ostream = ostream;
    		}
    
    		@Override
    		public void write(int b) {
    			// 将数据写到 stream 中
    			ostream.write(b);
    		}
    
    		@Override
    		public boolean isReady() {
    			return false;
    		}
    
    		@Override
    		public void setWriteListener(WriteListener writeListener) {
    		}
    
    	}
    }
    

    I18nPropertiesStrategy.java

    package com.blog.www.bean.i18n;
    
    
    /**
     * 国际化属性文件策略
     * <br/>
     * @author     :leigq
     * @date       :2019/7/24 13:04
     */
    public interface I18nPropertiesStrategy {
    
    	String getValue(String key);
    
    }
    

    CnI18nPropertiesStrategy.java

    package com.blog.www.bean.i18n;
    
    
    import com.blog.www.util.PropertiesUtils;
    
    import java.util.Objects;
    
    /**
     * 中国国际化属性文件策略
     * <br/>
     *
     * @author :leigq
     * @date :2019/7/24 13:04
     */
    @Component
    public class CnI18nPropertiesStrategy implements I18nPropertiesStrategy {
    
    	private volatile static PropertiesUtils propertiesUtils;
    
    	@Override
    	public String getValue(String key) {
    		return getPropertiesUtils().getValue(key);
    	}
    
    	private PropertiesUtils getPropertiesUtils() {
    		if (Objects.isNull(propertiesUtils)) {
    			synchronized (CnI18nPropertiesStrategy.class) {
    				if (Objects.isNull(propertiesUtils)) {
    					propertiesUtils = PropertiesUtils.init("i18n/language_zh_CN.properties");
    				}
    			}
    		}
    		return propertiesUtils;
    	}
    }
    
    

    EnI18nPropertiesStrategy.java

    package com.blog.www.bean.i18n;
    
    
    import com.blog.www.util.PropertiesUtils;
    
    import java.util.Objects;
    
    /**
     * 英文国际化属性文件策略
     * <br/>
     *
     * @author :leigq
     * @date :2019/7/24 13:04
     */
    @Component
    public class EnI18nPropertiesStrategy implements I18nPropertiesStrategy {
    
    	private volatile static PropertiesUtils propertiesUtils;
    
    	@Override
    	public String getValue(String key) {
    		return getPropertiesUtils().getValue(key);
    	}
    
    	private PropertiesUtils getPropertiesUtils() {
    		if (Objects.isNull(propertiesUtils)) {
    			synchronized (EnI18nPropertiesStrategy.class) {
    				if (Objects.isNull(propertiesUtils)) {
    					propertiesUtils = PropertiesUtils.init("i18n/language_en_US.properties");
    				}
    			}
    		}
    		return propertiesUtils;
    	}
    }
    
    

    PropertiesUtils.java

    package com.blog.www.util;
    
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.support.PropertiesLoaderUtils;
    
    import java.io.IOException;
    import java.util.Objects;
    import java.util.Properties;
    
    /**
     * 读取Properties文件工具类
     * <br>
     * 参考:<a href='https://note.youdao.com/share/?id=1a9228ce7c45ad383921eccf6c0580f2&type=note#/'>spring中如何读取.properties配置文件</a>
     * <p>
     * 创建人:leigq <br>
     * 创建时间:2018-11-09 13:17 <br>
     * <p>
     * 修改人: <br>
     * 修改时间: <br>
     * 修改备注: <br>
     * </p>
     */
    public class PropertiesUtils {
    
    	private Logger log = LoggerFactory.getLogger(PropertiesUtils.class);
    
    	private String path;
    	private Properties properties;
    
    	private PropertiesUtils(String path) {
    		this.path = path;
    		try {
    			this.properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource(path));
    		} catch (IOException e) {
    			log.error(String.format("地址为 %s 的文件不存在", path), e);
    		}
    	}
    
    	/**
    	 * 构建PropertiesUtil
    	 * <br>创建人: leigq
    	 * <br>创建时间: 2018-11-09 13:56
    	 * <br>
    	 *
    	 * @param path 资源文件路径,如:i18n/zh_CN.properties
    	 */
    	public static PropertiesUtils init(String path) {
    		return new PropertiesUtils(path);
    	}
    
    	/**
    	 * 获取配置文件中的值
    	 * <br>创建人: leigq
    	 * <br>创建时间: 2018-11-09 14:05
    	 * <br>
    	 *
    	 * @param key 键
    	 */
    	public String getValue(String key) {
    		if (StringUtils.isBlank(key)) {
    			throw new NullPointerException(String.format("配置文件 %s 中找不到这个Key,key = %s", path, key));
    		}
    		key = key.trim();
    		String property = properties.getProperty(key);
    		if (StringUtils.isBlank(property)) {
    			throw new NullPointerException(String.format("配置文件 %s 中 key = %s 的 value 为空", path, key));
    		}
    		return property.trim();
    	}
    }
    
    

    JSONUtils.java

    package com.blog.www.util;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.core.type.TypeReference;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * Jackson 工具类
     * </br>
     * 参考:https://www.cnblogs.com/xiezhenwei/p/3616576.html
     * <p>
     * 创建人:LeiGQ <br>
     * 创建时间:2019-05-10 15:44 <br>
     * <p>
     * 修改人: <br>
     * 修改时间: <br>
     * 修改备注: <br>
     * </p>
     */
    public final class JSONUtils {
    
        private final static ObjectMapper objectMapper = new ObjectMapper();
    
        private JSONUtils() {
        }
    
        public static ObjectMapper getInstance() {
            return objectMapper;
        }
    
        /**
         * javaBean,list,array convert to json string
         */
        public static String obj2json(Object obj)
                throws JsonProcessingException {
            return objectMapper.writeValueAsString(obj);
        }
    
        /**
         * json string convert to javaBean
         */
        public static <T> T json2pojo(String jsonStr, Class<T> clazz)
                throws IOException {
            return objectMapper.readValue(jsonStr, clazz);
        }
    
        /**
         * json string convert to map
         */
        public static <T> Map json2map(String jsonStr)
                throws IOException {
            return objectMapper.readValue(jsonStr, Map.class);
        }
    
        /**
         * json string convert to map with javaBean
         */
        public static <T> Map<String, T> json2map(String jsonStr, Class<T> clazz)
                throws IOException {
            Map<String, Map<String, Object>> map = objectMapper.readValue(jsonStr,
                    new TypeReference<Map<String, T>>() {
                    });
            Map<String, T> result = new HashMap<>();
            for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
                result.put(entry.getKey(), map2pojo(entry.getValue(), clazz));
            }
            return result;
        }
    
        /**
         * json array string convert to list with javaBean
         */
        public static <T> List<T> json2list(String jsonArrayStr, Class<T> clazz)
                throws IOException {
            List<Map<String, Object>> list = objectMapper.readValue(jsonArrayStr,
                    new TypeReference<List<T>>() {
                    });
            List<T> result = new ArrayList<T>();
            for (Map<String, Object> map : list) {
                result.add(map2pojo(map, clazz));
            }
            return result;
        }
    
        /**
         * map convert to javaBean
         */
        public static <T> T map2pojo(Map map, Class<T> clazz) {
            return objectMapper.convertValue(map, clazz);
        }
    }
    

    如果需要新增一种语言的话,只需新建一个class,实现 I18nPropertiesStrategy 接口即可。

    5、使用

    我们只需要把返回结果中的内容使用 properties 文件中的 key 替换即可,如有动态数据用 {} 填充即可。

    感谢


    作者:不敲代码的攻城狮
    出处:https://www.cnblogs.com/leigq/
    任何傻瓜都能写出计算机可以理解的代码。好的程序员能写出人能读懂的代码。

     
  • 相关阅读:
    [hdu3853]LOOPS(概率dp)
    [poj2096]Collecting Bugs(概率dp)
    lintcode-42-最大子数组 II
    lintcode-39-恢复旋转排序数组
    lintcode-36-翻转链表 II
    lintcode-34-N皇后问题 II
    lintcode-33-N皇后问题
    lintcode-32-最小子串覆盖
    lintcode-31-数组划分
    lintcode-30-插入区间
  • 原文地址:https://www.cnblogs.com/leigq/p/13406588.html
Copyright © 2011-2022 走看看