Springboot国际化方案 (非MessageSource)

Springboot国际化方案 (非MessageSource)

一、实现思路(响应切面处理器+全局异常处理器)

1.定义响应切面处理器

@Override
	public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
		return isSupport(methodParameter);
	}


	@Override
	public R beforeBodyWrite(R r, MethodParameter methodParameter, MediaType mediaType,
							 Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
							 ServerHttpResponse serverHttpResponse) {

        //手动翻译文本
		String response_msg = TranslateExecuteWrapper.translateText("response_msg", r.getMsg());
		r.setMsg(response_msg);
        //手动翻译对象
		TranslateExecuteWrapper.translateObject(r.getData());
		return r;
	}

	/**
	 * 仅支持返回值类型为R  并且排除异常处理器类型 防止重复处理异常信息
	 * @param methodParameter
	 * @return
	 */
	private boolean isSupport(MethodParameter methodParameter) {
		return methodParameter.getMethod().getAnnotation(ExceptionHandler.class) == null && methodParameter.getParameterType().isAssignableFrom(R.class);
	}

2.全局异常解析器

	private String executeTranslate(String code, Integer statusCode, String exceptionMsg) {
		return TranslateExecuteWrapper.translateText(exceptionCode, code, prodI18nErrorParam(statusCode, exceptionMsg));
	}


	private Map<String, String> prodI18nErrorParam(Integer code, String exceptionMsg) {
		Map<String, String> params = new HashMap<>();
		params.put("code", String.valueOf(code));
		params.put("msg", exceptionMsg);
		return params;
	}

	@ExceptionHandler(Exception.class)
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	public R<String> handleGlobalException(Exception e) {
		log.error("全局异常信息 ex={}", e.getMessage(), e);
		globalExceptionHandler.handle(e);
		// 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
		String errorMsg = GlobalConstants.ENV_PROD.equals(profile) ? PROD_ERR_MSG
				: (e instanceof NullPointerException ? NLP_MSG : e.getLocalizedMessage());
		String exceptionMsg = executeTranslate("global.exception", SystemResultCode.BAD_REQUEST.getCode(), errorMsg);
		return R.failed(SystemResultCode.SERVER_ERROR, exceptionMsg);
	}

二、主要工具类

TranslateExecuteWrapper里面包含文本翻译 与对象翻译

不指定语言 默认使用上下文语言环境

文本翻译

//翻译文本指定业务码  key
public static String translateText(String businessCode, String code);
//翻译文本指定业务码 key 语言环境
public static String translateText(String businessCode, String code, String language)
//翻译文本指定业务码 key 模板参数  
public static String translateText(String businessCode, String code, Map<String, String> params)
//翻译文本指定业务码 key 语言环境 参数    
public static String translateText(String businessCode, String code, String language, Map<String, String> params)    

对象翻译

//翻译对象 传递对象
public static void translateObject(Object source)
//翻译对象 传递对象  与上下文参数      
public static void translateObject(Object source, Map<String, String> params)    

三、结果展示

实体类

	@I18nField(businessCode = "dict")
	private String title;

	@I18nField(businessCode = "dict", rangeValue = { "是", "否" })
	private String status;

	@I18nField(businessCode = "dict", rangeValue = { "开", "关" }, defaultValue = "不开")
	private String open;

	/**
	 * 字典不存在 会放入空标记到缓存
	 */
	@I18nField(businessCode = "dict")
	private String nullValue;

	/**
	 * 若list内为基础类型 或String 则跳过
	 */
	private List<String> stringList;

	/**
	 * 递归翻译
	 */
	private List<I18nUser> users;

	/**
	 * 递归翻译
	 */
	private Set<I18nUser> userSet;

赋值实体

I18nUser i18nUser = new I18nUser().setTitle("测试").setOpen("开").setStatus("是");
		I18nUser i18nUserListElement = new I18nUser().setTitle("测试").setOpen("关").setStatus("否");
		i18nUser.setUsers(ListUtil.of(i18nUserListElement));
		I18nUser clone = ObjectUtil.clone(i18nUserListElement);
		HashSet<I18nUser> objects = new HashSet<>();
		objects.add(clone);
		i18nUser.setUserSet(objects);
		List<String> of = ListUtil.of("测试", "开");
		i18nUser.setStringList(of);
		i18nUser.setNullValue("not_exist");

测试中文

请求头指定 lang: zh_CN

{
    "code": 200,
    "message": "成功",
    "data": {
        "title": "测试",
        "status": "是",
        "open": "开",
        "nullValue": "not_exist",
        "stringList": [
            "测试",
            "开"
        ],
        "users": [
            {
                "title": "测试",
                "status": "否",
                "open": "关",
                "nullValue": "",
                "stringList": [],
                "users": [],
                "userSet": []
            }
        ],
        "userSet": [
            {
                "title": "测试",
                "status": "否",
                "open": "关",
                "nullValue": "",
                "stringList": [],
                "users": [],
                "userSet": []
            }
        ]
    }
}

测试英文

请求头指定 lang: en_US

{
    "code": 200,
    "message": "Success",
    "data": {
        "title": "Test",
        "status": "Yes",
        "open": "Open",
        "nullValue": "not_exist",
        "stringList": [
            "测试",
            "开"
        ],
        "users": [
            {
                "title": "Test",
                "status": "No",
                "open": "Close",
                "nullValue": "",
                "stringList": [],
                "users": [],
                "userSet": []
            }
        ],
        "userSet": [
            {
                "title": "Test",
                "status": "No",
                "open": "Close",
                "nullValue": "",
                "stringList": [],
                "users": [],
                "userSet": []
            }
        ]
    }
}

四、快速上手

一、建表语句

CREATE TABLE `i18n_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `system_name` varchar(20) DEFAULT NULL COMMENT '系统名称',
  `business_code` varchar(20) DEFAULT NULL COMMENT '业务码',
  `code` varchar(100) DEFAULT NULL COMMENT 'code',
  `language` varchar(20) DEFAULT NULL COMMENT '语言环境 zh_CN',
  `value` varchar(50) DEFAULT NULL COMMENT '值',
  `type` tinyint(4) DEFAULT NULL COMMENT '类型 1 明文 2模板',
  `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_uni_code_language` (`code`,`language`),
  UNIQUE KEY `idx_uni_sys_bs_code_language` (`system_name`,`business_code`,`code`,`language`)
) ENGINE=InnoDB AUTO_INCREMENT=137 DEFAULT CHARSET=utf8 COMMENT='i18n数据表'

INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (1, 'test-item', 'dict', '是', 'zh_CN', '是', 1, '2021-04-02 09:31:36', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (2, 'test-item', 'dict', '是', 'en_US', 'Yes', 1, '2021-04-02 09:32:01', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (3, 'test-item', 'dict', '否', 'zh_CN', '否', 1, '2021-04-02 09:33:55', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (4, 'test-item', 'dict', '否', 'en_US', 'No', 1, '2021-04-02 09:34:18', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (5, 'test-item', 'dict', '开', 'zh_CN', '开', 1, '2021-04-02 09:33:55', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (6, 'test-item', 'dict', '开', 'en_US', 'Open', 1, '2021-04-02 09:34:18', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (7, 'test-item', 'dict', '关', 'zh_CN', '关', 1, '2021-04-02 09:33:55', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (8, 'test-item', 'dict', '关', 'en_US', 'Close', 1, '2021-04-02 09:34:18', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (9, 'test-item', 'dict', '测试', 'zh_CN', '测试', 1, '2021-04-02 09:33:55', NULL);
INSERT INTO `i18n_data`(`id`, `system_name`, `business_code`, `code`, `language`, `value`, `type`, `create_time`, `update_time`) VALUES (10, 'test-item', 'dict', '测试', 'en_US', 'Test', 1, '2021-04-02 09:34:18', NULL);

二、引入relaxed-spring-boot-starter-i18n坐标

<dependency>
	<groupId>com.lovecyy</groupId>
	<artifactId>relaxed-spring-boot-starter-i18n</artifactId>
	<version>${revision}</version>
</dependency>

三、配置application.yml

spring:
  application:
    name: relaxed-samples-i18n
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://relaxed-mysql:3306/ballcat?serverTimezone=Asia/Shanghai&useLegacyDatetimeCode=false&nullNamePatternMatchesAll=true&zeroDateTimeBehavior=CONVERT_TO_NULL&tinyInt1isBit=false&autoReconnect=true&useSSL=false&pinGlobalTxToPhysicalConnection=true
    username: root
    password: 123456
  redis:
    host: relaxed-redis
    password: '123456'
    port: 8007
# mybatis-plus相关配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*Mapper.xml
  global-config:
    banner: false


relaxed:
  i18n:
    #系统名称
    systemName: test-item
    #缓存空值标记 防止缓存穿透
    nullValue: N_V
    #执行器 默认使用simple 推荐cache
    executor: cache
    #key 生成器分隔符
    generate:
      delimiter: ':'
    #缓存配置    
    cache:
      #默认local  
      type: redis
      #过期时间 local 默认无限 cache 小于0 则永不过期
      expire: -1

四、实体类

1.R 通用返回
@Data
public class R {

	private Integer code;

	private String msg;

	private Object data;

	public R() {

	}

	public R(Integer code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	public R(Integer code, String msg, Object data) {
		this.code = code;
		this.msg = msg;
		this.data = data;
	}

	public static R ok() {
		return new R(200, "Success");
	}

	public static R ok(Object data) {
		return new R(200, "Success", data);
	}

	public static R fail(String msg) {
		return new R(500, msg, null);
	}

}

2.I18nUser
@Accessors(chain = true)
@Data
public class I18nUser implements Serializable {

	@I18nField(businessCode = "dict")
	private String title;

	@I18nField(businessCode = "dict", rangeValue = { "是", "否" })
	private String status;

	@I18nField(businessCode = "dict", rangeValue = { "开", "关" }, defaultValue = "不开")
	private String open;

	/**
	 * 字典不存在 会放入空标记到缓存
	 */
	@I18nField(businessCode = "dict")
	private String nullValue;

	/**
	 * 若list内为基础类型 或String 则跳过
	 */
	private List<String> stringList;

	/**
	 * 递归翻译
	 */
	private List<I18nUser> users;

	/**
	 * 递归翻译
	 */
	private Set<I18nUser> userSet;

}

五、配置区域解析器

请求头要指定语言 lang: zh_CN

/**
 * 国际化配置
 *
 * @author Yakir
 */
@Configuration
public class LocaleConfig {

	/**
	 * 区域解析器
	 */
	@Bean
	public LocaleResolver localeResolver() {
		MyLocaleResolver localeResolver = new MyLocaleResolver();
		return localeResolver;
	}

	class MyLocaleResolver implements LocaleResolver {

		@Override
		public Locale resolveLocale(HttpServletRequest request) {
			String language = request.getHeader("lang");
			if (StrUtil.isEmpty(language)) {
				// 路径上没有国际化语言参数,采用默认的(从请求头中获取)
				return request.getLocale();
			}
			else {
				// 格式语言_国家 en_US
				return StringUtils.parseLocale(language);
			}
		}

		@Override
		public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
				Locale locale) {

		}

	}

}

六、配置I18nResponseAdvice

/**
 * 只针对R的msg进行国际化处理
 *
 * @author Yakir
 */
@RequiredArgsConstructor
@RestControllerAdvice
public class I18nResponseAdvice implements ResponseBodyAdvice<R> {


	@Override
	public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
		return isSupport(methodParameter);
	}



	@Override
	public R beforeBodyWrite(R r, MethodParameter methodParameter, MediaType mediaType,
							 Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
							 ServerHttpResponse serverHttpResponse) {


		String response_msg = TranslateExecuteWrapper.translateText("response_msg", r.getMsg());
		r.setMsg(response_msg);
		TranslateExecuteWrapper.translateObject(r.getData());
		return r;
	}

	/**
	 * 仅支持返回值类型为R 与不为错误处理器返回过来的类型
	 * @param methodParameter
	 * @return
	 */
	private boolean isSupport(MethodParameter methodParameter) {
		return methodParameter.getMethod().getAnnotation(ExceptionHandler.class) == null && methodParameter.getParameterType().isAssignableFrom(R.class);
	}


}

七、实现I18nDataProvider数据提供接口

@Override
	public I18nItem selectOne(String systemName, String businessCode, String code, String language) {
		I18nData i18nData = i18nDataService.getOne(Wrappers.lambdaQuery(I18nData.class)
				.eq(I18nData::getSystemName, systemName).eq(I18nData::getBusinessCode, businessCode)
				.eq(I18nData::getCode, code).eq(I18nData::getLanguage, language));
		return convertToI18nItem(i18nData);
	}

	private I18nItem convertToI18nItem(I18nData i18nData) {
		if (i18nData == null) {
			return null;
		}
		return new I18nItem().setSystemName(i18nData.getSystemName()).setBusinessCode(i18nData.getBusinessCode())
				.setCode(i18nData.getCode()).setLanguage(i18nData.getLanguage()).setType(i18nData.getType())
				.setValue(i18nData.getValue());
	}

八、配置全局异常解析器

主要负责对异常信息进行国际化

private String executeTranslate(String code, Integer statusCode, String exceptionMsg) {
		return TranslateExecuteWrapper.translateText(exceptionCode, code, prodI18nErrorParam(statusCode, exceptionMsg));
	}


	private Map<String, String> prodI18nErrorParam(Integer code, String exceptionMsg) {
		Map<String, String> params = new HashMap<>();
		params.put("code", String.valueOf(code));
		params.put("msg", exceptionMsg);
		return params;
	}

	@ExceptionHandler(Exception.class)
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	public R<String> handleGlobalException(Exception e) {
		log.error("全局异常信息 ex={}", e.getMessage(), e);
		globalExceptionHandler.handle(e);
		// 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
		String errorMsg = GlobalConstants.ENV_PROD.equals(profile) ? PROD_ERR_MSG
				: (e instanceof NullPointerException ? NLP_MSG : e.getLocalizedMessage());
		String exceptionMsg = executeTranslate("global.exception", SystemResultCode.BAD_REQUEST.getCode(), errorMsg);
		return R.failed(SystemResultCode.SERVER_ERROR, exceptionMsg);
	}

九、测试国际化

	@GetMapping("/json/serialize")
	public R testWebContentTJsonSerialize() {
		I18nUser i18nUser = new I18nUser().setTitle("测试").setOpen("开").setStatus("是");
		I18nUser i18nUserListElement = new I18nUser().setTitle("测试").setOpen("关").setStatus("否");
		i18nUser.setUsers(ListUtil.of(i18nUserListElement));
		I18nUser clone = ObjectUtil.clone(i18nUserListElement);
		HashSet<I18nUser> objects = new HashSet<>();
		objects.add(clone);
		i18nUser.setUserSet(objects);
		List<String> of = ListUtil.of("测试", "开");
		i18nUser.setStringList(of);
		i18nUser.setNullValue("not_exist");
		return R.ok(i18nUser);
	}

	@GetMapping("/json/exception")
	public R testException() {
		throw new RuntimeException( "mysql connection faild");
	}

附: demo地址:https://gitee.com/TomSale/relaxed.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值