1.目标:业务数据中的某些字段列,需要根据用户选择的语言类型自动处理后返回对应语言的数据
2.背景:系统需要适应多种语言:中文简体、中文繁体、英语等,一个业务数据中的某些列,会同时设置多种语言的数据,用户可根据自己喜欢的语言展示对应的数据
3.用户选择语言后,前端把该参数通过header头传递到后端,后端查询业务数据后,根据header头中的多语言参数,查询业务数据中某些列对应语言的数据,再覆盖到当前的数据中,然后返回给客户端
4.难点:1.结果集的数据有数组嵌套,2.尽量少改动业务代码,且提供好的性能,来满足业务要求,因此通过aop + 注解 的方式统一处理这种类型的业务
5.实现逻辑:返回的结果集对象中有List集合,在aop切面中把结果集获取出来,并循环结果集中的每一列,判断是否为多语言字段,或者是否为List对象,把所有需要多语言处理的字段全部放到一个List集合中,通过mysql in 查询一次把数据查询出来,并组装数据为map 键值对结构,再次对结果集进行循环,再次使用反射,对需要多语言处理的字段通过key在map 集合中查询,有数据则覆盖现有数据
6.实现方法:1.在返回实体字段中添加注解,标识某些字段是多语言字段,2.在业务方法上使用注解,标识该方法需要进行多语言处理,3.添加aop切面,对服务层使用了多语言处理注解的方法进行横切,来统一多语言处理
6.代码实现
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiLanguageAnnotation {
/**
* 表名
*/
String tabName() default "";
/**
* 列名
*/
String colName() default "";
/**
* 主键名
* @return
*/
String pkName() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiLanguageResponse {
}
package com.es.util;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.egovchina.apicenter.es.config.MultiLanguageAnnotation;
import com.egovchina.apicenter.es.searchlogic.po.TabMultiLanguagePo;
import com.egovchina.apicenter.es.searchlogic.service.MultiLanguageService;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
/**
* 多语言帮助类
*/
public class MultiLanguageUtil {
/**
* 设置多语言内容
* 两种使用示例:
* 1.直接在方法上使用 @MultiLanguageResponse 注解,来处理多语言
* 2.使用代码方式:
List list = esDatas.getDatas();
String languageCode = RequestHolder.getDataLanguageHeaderValue();
MultiLanguageUtil.setMultiLanguageInfo(multiLanguageService , list , languageCode);
*
* @param multiLanguageService es多语言查询服务
* @param documents 要设置多语言字段的文档集合
* @param languageCode 语言类型
* @param
*/
public static <T> void setMultiLanguageInfo(MultiLanguageService multiLanguageService , List<T> documents , String languageCode){
if(documents != null && documents.size() > 0){
List<String> multiInfoIds = getMultiInfoId(documents , languageCode);
//System.out.println("util multiInfoIds:" + JSON.toJSONString(multiInfoIds));
if(multiInfoIds != null && multiInfoIds.size() > 0){
//过滤重复multiInfoId
multiInfoIds = multiInfoIds.stream().distinct().collect(Collectors.toList());
//根据multiInfoId查询多语言内容
List<TabMultiLanguagePo> multiLanguageDocuments = multiLanguageService.getByMultiInfoId(multiInfoIds);
if(multiLanguageDocuments != null && multiLanguageDocuments.size() > 0) {
//把从es查询出来的多语言键值对,序列化成map
Map<String , String> map = new HashMap<>(multiLanguageDocuments.size());
multiLanguageDocuments.forEach(item -> map.put(item.getMultiInfoId() , item.getContentText()));
//System.out.println("util map:" + JSON.toJSONString(map));
//设置多语言内容
setMultiValue(documents , languageCode , map);
}
}
}
}
/**
* 获取文档中有多语言注解的 multiInfoId
* @param documents
* @param languageCode
* @param
* @return
*/
private static <T> List<String> getMultiInfoId(List<T> documents , String languageCode){
List<String> multiInfoIdList = new ArrayList<>(20);
documents.forEach(item -> {
try {
Class clazz = item.getClass(); // 获取集合中的对象
Field[] fields = clazz.getDeclaredFields();// 获取他的字段数组
for (Field field : fields) {
field.setAccessible(true); // 设置字段可访问
//获取带有 MultiLanguageAnnotation 注解的字段,并组装 multiInfoId
MultiLanguageAnnotation languageAnnotation = field.getDeclaredAnnotation(MultiLanguageAnnotation.class);
if (languageAnnotation != null) {
String multiInfoId = languageAnnotation.tabName() + languageAnnotation.colName() +
ReflectionUtils.getFieldValue(item, languageAnnotation.pkName()) + languageCode;
//System.out.println("multiInfoId:" + multiInfoId);
multiInfoIdList.add(multiInfoId);
}
//获取List泛型中带有 MultiLanguageAnnotation 注解的字段,并组装 multiInfoId
if (List.class.isAssignableFrom(field.getType())) {
Type genericType = field.getGenericType(); // 当前集合的泛型类型
if (null == genericType) {
continue;
}
if (genericType instanceof ParameterizedType) {
//ParameterizedType pt = (ParameterizedType) genericType;
//Class clz = (Class) pt.getActualTypeArguments()[0];//得到对象list中实例的类型
Object obj = field.get(item);
if(null == obj){
continue;
}
Class childClazz = obj.getClass(); //获取到属性的值的Class对象
if(null == childClazz){
continue;
}
Method m = childClazz.getDeclaredMethod("size");
int size = (Integer) m.invoke(field.get(item)); //调用list的size方法,得到list的长度
for (int i = 0; i < size; i++) {
//遍历list,调用get方法,获取list中的对象实例
Method getM = childClazz.getDeclaredMethod("get", int.class);
//getM.setAccessible(true);
Object childObj = getM.invoke(field.get(item), i); //获取泛型的子类对象
Field[] childFields = childObj.getClass().getDeclaredFields(); //得到子类对象的字段数组
//System.out.println("list obj field size : " + objField.length);
for (Field childField : childFields){
childField.setAccessible(true);
MultiLanguageAnnotation childLanguageAnnotation = childField.getDeclaredAnnotation(MultiLanguageAnnotation.class);
if (childLanguageAnnotation != null) {
String childMultiInfoId = childLanguageAnnotation.tabName() + childLanguageAnnotation.colName() +
ReflectionUtils.getFieldValue(childObj, childLanguageAnnotation.pkName()) + languageCode;
//System.out.println("childMultiInfoId:" + childMultiInfoId);
multiInfoIdList.add(childMultiInfoId);
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
return multiInfoIdList;
}
/**
* 根据多语言 multiInfoId ,设置多语言内容
* @param documents
* @param map
* @param
*/
private static <T> void setMultiValue(List<T> documents , String languageCode , Map<String , String> map){
documents.forEach(item -> {
try {
Class clazz = item.getClass(); // 获取集合中的对象
Field[] fields = clazz.getDeclaredFields();// 获取他的字段数组
for (Field field : fields) {
field.setAccessible(true); // 设置字段可访问
//获取带有 MultiLanguageAnnotation 注解的字段,并组装 multiInfoId
MultiLanguageAnnotation languageAnnotation = field.getDeclaredAnnotation(MultiLanguageAnnotation.class);
if (languageAnnotation != null) {
String multiInfoId = languageAnnotation.tabName() + languageAnnotation.colName() +
ReflectionUtils.getFieldValue(item, languageAnnotation.pkName()) + languageCode;
String contentText = map.get(multiInfoId);
if(!StringUtils.isEmpty(contentText)){
field.set(item , contentText);
contentText = null;
}
}
//获取List泛型中带有 MultiLanguageAnnotation 注解的字段,并组装 multiInfoId
if (List.class.isAssignableFrom(field.getType())) {
Type genericType = field.getGenericType(); // 当前集合的泛型类型
if (null == genericType) {
continue;
}
if (genericType instanceof ParameterizedType) {
//ParameterizedType pt = (ParameterizedType) genericType;
//Class clz = (Class) pt.getActualTypeArguments()[0];//得到对象list中实例的类型
Object obj = field.get(item);
if(null == obj){
continue;
}
Class childClazz = obj.getClass(); //获取到属性的值的Class对象
if(null == childClazz){
continue;
}
Method m = childClazz.getDeclaredMethod("size");
int size = (Integer) m.invoke(field.get(item)); //调用list的size方法,得到list的长度
for (int i = 0; i < size; i++) {
//遍历list,调用get方法,获取list中的对象实例
Method getM = childClazz.getDeclaredMethod("get", int.class);
Object childObj = getM.invoke(field.get(item), i); //获取泛型的子类对象
Field[] childFields = childObj.getClass().getDeclaredFields(); //得到子类对象的字段数组
//System.out.println("list obj field size : " + objField.length);
for (Field childField : childFields){
childField.setAccessible(true);
MultiLanguageAnnotation childLanguageAnnotation = childField.getDeclaredAnnotation(MultiLanguageAnnotation.class);
if (childLanguageAnnotation != null) {
String childMultiInfoId = childLanguageAnnotation.tabName() + childLanguageAnnotation.colName() +
ReflectionUtils.getFieldValue(childObj, childLanguageAnnotation.pkName()) + languageCode;
String childContentText = map.get(childMultiInfoId);
if(!StringUtils.isEmpty(childContentText)){
childField.set(childObj , childContentText);
childContentText = null;
}
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
@Component
@Slf4j
@Aspect
public class MultiLanguageAspect {
@Autowired
private MultiLanguageService multiLanguageService;
/**
* 监听有多语言响应注解的方法,对结果集中的多语言字段进行处理
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around(value = "@annotation(com.apicenter.es.config.MultiLanguageResponse)")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object result = proceedingJoinPoint.proceed();
//取到header头信息中的多语言参数字段
String languageCode = RequestHolder.getDataLanguageHeaderValue();
if(!StringUtils.isEmpty(languageCode)){
//只对返回值类型为:ResultTemplate、List 两种类型的结果集进行多语言处理
if(result instanceof ResultTemplate){
ResultTemplate resultTemplate = (ResultTemplate)result;
MultiLanguageUtil.setMultiLanguageInfo(multiLanguageService , resultTemplate.getData() , languageCode);
} else if(result instanceof List){
List list = (List)result;
MultiLanguageUtil.setMultiLanguageInfo(multiLanguageService , list , languageCode);
}
}
return result;
}
}
public class RequestHolder {
private static HttpServletRequest getHttpServletRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
/**
* 获取多语言header参数
* @return
*/
public static String getDataLanguageHeaderValue() {
HttpServletRequest httpServletRequest = getHttpServletRequest();
if (httpServletRequest != null) {
String header = httpServletRequest.getHeader("Data-Language");
if(!StringUtils.isEmpty(header)){
return header;
}
}
return ""; //默认返回空
}
}
public class ReflectionUtils {
private static Logger log = LoggerFactory.getLogger(ReflectionUtils.class);
/**
* 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
*
* @param object
* @param fieldName
* @return
*/
public static Object getFieldValue(Object object, String fieldName) {
Field field = getDeclaredField(object, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
}
makeAccessible(field);
Object result = null;
try {
result = field.get(object);
} catch (IllegalAccessException e) {
log.error("setFieldValue:", e);
}
return result;
}
/**
* 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
*
* @param object
* @param fieldName
* @param value
*/
public static void setFieldValue(Object object, String fieldName, Object value) {
Field field = getDeclaredField(object, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
}
makeAccessible(field);
try {
field.set(object, value);
} catch (IllegalAccessException e) {
log.error("setFieldValue:", e);
}
}
/**
* 通过反射, 获得定义 Class 时声明的父类的泛型参数的类型
* 如: public EmployeeDao extends BaseDao
*
* @param clazz
* @param index
* @return
*/
@SuppressWarnings("unchecked")
public static Class getSuperClassGenricType(Class clazz, int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
return Object.class;
}
if (!(params[index] instanceof Class)) {
return Object.class;
}
return (Class) params[index];
}
/**
* 循环向上转型, 获取对象的 DeclaredMethod
*
* @param object
* @param methodName
* @param parameterTypes
* @return
*/
public static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes) {
for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
return superClass.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
//Method 不在当前类定义, 继续向上转型
}
}
return null;
}
/**
* 使 filed 变为可访问
*
* @param field
*/
public static void makeAccessible(Field field) {
if (!Modifier.isPublic(field.getModifiers())) {
field.setAccessible(true);
}
}
/**
* 循环向上转型, 获取对象的 DeclaredField
*
* @param object
* @param filedName
* @return
*/
public static Field getDeclaredField(Object object, String filedName) {
for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
return superClass.getDeclaredField(filedName);
} catch (NoSuchFieldException e) {
//Field 不在当前类定义, 继续向上转型
}
}
return null;
}
}
@MultiLanguageResponse
@Override
public ResultTemplate<AffairsDocument> affairs(AffairsDto affairsDto) {
//xxx 业务处理
return ResultTemplateUtil.ofESDatasAndPage(data);
}