java 拦截器实现动态数据国际化多语言切换
很多场景下,咱们开发的项目需要进行语言的切换,但在每个接口中去通过获取请求头中的语言标识。查询数据时,根据语言标识过滤掉不匹配的字段,只查询与当前语言标识匹配的字段和公用字段。会导致大量冗余代码,许多与业务无关的代码会混入业务逻辑中,增加维护难度和开发成本。
所以想到通过拦截器的方式,在查询底层数据的时候,不需要关心语言类型,查询返回时,拦截器中对接口返回的数据进行国际化的处理,从而实现动态数据国际化的处理。
比如现在有一个web端项目,在登录的时候通过选择语言,对应进入不同语言版本的系统,显示不同语言的菜单管理。那么,系统菜单表sys_menu表中设计的字段包括:
| 字段名称 | 字段类型 | 字段描述 |
| menu_code | varchar(20) | 菜单编码 |
| title | varchar(20) | 菜单名称 |
| cn_title | varchar(20) | 菜单中文名称 |
| en_title | varchar(20) | 菜单英文名称 |
| th_title | varchar(20) | 菜单泰文名称 |
使用拦截器和JsonNode树模型进行统一处理,如下:
创建一个拦截器,对所有controller层的接口请求进行处理:
1、创建一个名为MultiLanguageAdvice的类,实现ResponseBodyAdvice接口,用于拦截响应数据。在beforeBodyWrite方法中,通过JsonNode树模型对所有需要国际化处理的数据进行解析和处理,递归遍历响应结构。
2、在代码中维护一个语言前缀列表,使用时将其转换为小写,用于后面过滤带有语言前缀的字段,使用HttpServletRequest获取到请求头中的语言前缀language,将其从语言前缀列表中移除,再将body转换为JsonNode,递归调用自定义的方法responseDataParseAndRemove()进行字段移除和重组。
@Slf4j
@RestControllerAdvice
public class MultiLanguageAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 通过HttpServletRequest获取到用户当前的语言环境
ServletRequestAttributes servletRequestAttributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
if (servletRequestAttributes != null) {
HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
// 本地维护一个语言前缀列表
List<String> languageList = new ArrayList<>();
languageList.add("CN");//中文
languageList.add("EN");//英文
languageList.add("TH");//泰文
// 将响应数据body序列化为JsonNode
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = objectMapper.readTree(objectMapper.writeValueAsString(body));
//接口的header中传的有语言参数,才做这种特殊处理
String language = httpServletRequest.getHeader("language");
if (StringUtils.isNotBlank(language)){
languageList.remove(language);
responseDataParseAndRemove(node, languageList, language);
}
return node;
}
return body;
}
}
3、在自定义方法responseDataParseAndRemove()中递归遍历所有字段,移除带有指定语言前缀的字段并新建目标字段,将当前语言标识的字段对应的值赋值给目标字段。
/**
* 响应数据解析和指定前缀字段移除
* @param node 响应数据节点
* @param toRemoveFieldPrefixList 待移除的字段前缀数组
* @description: 通过解析Json结构的响应数据、移除指定前缀字段、以达到动态数据在不同语言环境下的动态切换
**/
public static void responseDataParseAndRemove(JsonNode node, List<String> toRemoveFieldPrefixList, String language) {
// 节点只有两种:容器节点和非容器节点
if (node.isContainerNode()) {
// 判断该节点是对象还是数组
if (node.isObject()) {
List<String> currentLanguageFieldNameList = new ArrayList<>();
// 如果JsonNode是对象
ObjectNode objectNode = (ObjectNode) node;
// 待移除的字段列表初始化,本列表的作用是暂存所有满足条件待移除的字段
List<String> toRemoveFieldList = new ArrayList<>();
Iterator<Map.Entry<String, JsonNode>> nodeFieldList = objectNode.fields();
while (nodeFieldList.hasNext()) {
Map.Entry<String, JsonNode> nodeField = nodeFieldList.next();
String fieldName = nodeField.getKey();
JsonNode fieldValue = nodeField.getValue();
// 如果当前字段名的前缀属于待移除前缀列表中的任何一个元素,说明当前这个字段需要移除,加入到toRemoveFieldList
for (String toRemoveFieldPrefix : toRemoveFieldPrefixList) {
if (fieldName.startsWith(toRemoveFieldPrefix.toLowerCase())) {
toRemoveFieldList.add(fieldName);
}
}
// 继续判断字段值是不是一个对象,如果是则递归调用当前方法进行处理;如果是数组则循环递归调用当前方法进行处理
if (fieldValue.isObject()) {
responseDataParseAndRemove(fieldValue, toRemoveFieldPrefixList, language);
} else if (fieldValue.isArray()) {
ArrayNode arrayNode = (ArrayNode) fieldValue;
for (JsonNode element : arrayNode) {
responseDataParseAndRemove(element, toRemoveFieldPrefixList, language);
}
}
// 需要将当前语言的值替换到目标字段,所以维护一个含有当前语言前缀的字段和目标字段的
if (fieldName.startsWith(language.toLowerCase())) {
currentLanguageFieldNameList.add(fieldName);
}
}
// 一次性移除所有待移除的字段
objectNode.remove(toRemoveFieldList);
// 一次性替换所有字段
for (String fieldName : currentLanguageFieldNameList) {
// 新建一个目标字段:规则为原字段去掉当前语言前缀再转换为小驼峰,比如当前语言是cn,原字段是cnName,去掉前缀后就是再将首字母转换为小写就可以得到name
String targetFiledName = fieldName.substring(language.length());
targetFiledName = targetFiledName.substring(0, 1).toLowerCase() + targetFiledName.substring(1);
// 获取到原字段的值
JsonNode fieldValue = objectNode.get(fieldName);
// 将目标字段添加到树模型中,原字段的值作为目标字段的值
objectNode.set(targetFiledName, fieldValue);
// 移除原字段
objectNode.remove(fieldName);
}
} else if (node.isArray()) {
// 如果JsonNode是数组
ArrayNode arrayNode = (ArrayNode) node;
for (JsonNode element : arrayNode) {
responseDataParseAndRemove(element, toRemoveFieldPrefixList, language);
}
}
} else {
// 非容器节点
log.info("非容器节点,不需要任何处理");
}
}
测试效果看一看:

这样就完成了动态数据的国际化处理,根据不同的语言前缀返回对应语言的数据。其核心思想是在数据表中为不同语言建立相应的字段,然后通过语言前缀对响应数据进行处理。
更多推荐


所有评论(0)