使用过滤器实现后台返回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 替换即可,如有动态数据用 {} 填充即可。

感谢

posted @ 2019-01-11 21:24  leigq  阅读(521)  评论(0编辑  收藏  举报