package com.bc.exam.aspect; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.metadata.IPage; import com.fasterxml.jackson.annotation.JsonFormat; import com.bc.exam.core.annon.Dict; import com.bc.exam.core.api.ApiRest; import com.bc.exam.core.utils.Reflections; import com.bc.exam.modules.system.service.SysDictService; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 数据字典翻译AOP切面 *

* 拦截所有Controller方法的返回结果,自动将带有@Dict注解的字段从字典编码翻译为可读文本。 * 翻译后的文本以"字段名_dictText"的形式追加到返回的JSON对象中。 * 同时处理Date类型字段的格式化输出。 *

*

* 支持的数据结构: * - 单个对象:直接遍历字段进行翻译 * - 列表(List):遍历每个元素进行翻译 * - 分页结果(IPage):遍历分页记录进行翻译 * - 嵌套List字段:递归处理子列表中的字典翻译 *

*

* 注意:当前@Component注解被注释,如需启用字典翻译功能需取消注释或通过其他方式注册为Spring Bean。 *

* * @Description 描述:数据字典AOP类,处理数据字典值 * @Author C薛涵艺034244315 * @Date 20260616 */ @Aspect //@Component @Slf4j public class DictAspect { /** 字典服务,用于根据字典编码查询对应的可读文本 */ @Autowired private SysDictService sysDictService; /** * 环绕通知:切入所有Controller方法执行前后 *

* 切点表达式匹配com.bc.exam包下所有Controller类的public方法, * 在方法执行完成后对返回结果进行字典翻译处理。 *

* @param pjp 连接点对象,封装了目标方法的执行信息 * @return 经过字典翻译处理后的返回结果 * @throws Throwable 目标方法执行过程中抛出的异常 */ @Around("execution(public * com.bc.exam..*.*Controller.*(..))") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { return this.translate(pjp); } /** * 执行目标方法并对返回结果进行字典翻译 *

* 调用前必须确保SysDictService已正确配置并注入。 *

* @param pjp 连接点对象 * @return 翻译处理后的返回结果 * @throws Throwable 目标方法执行异常 */ public Object translate(ProceedingJoinPoint pjp) throws Throwable { // 先执行目标方法获取返回结果,再对结果进行字典翻译处理 return this.parseAllDictText(pjp.proceed()); } /** * 转换全部数据字典 *

* 仅对ApiRest类型的返回值进行字典翻译处理,其他类型直接透传。 *

* @param result Controller方法的返回结果 * @return 处理后的返回结果(ApiRest类型会被翻译,其他类型原样返回) */ private Object parseAllDictText(Object result) { // 仅ApiRest统一响应格式才进行字典翻译,非ApiRest类型直接返回不做处理 if (result instanceof ApiRest) { parseFullDictText(result); } return result; } /** * 解析并翻译ApiRest响应体中的所有数据字典字段 *

* 根据data字段的实际类型分三种情况处理: * 1. 分页对象(IPage):遍历分页记录逐条翻译 * 2. 列表对象(List):遍历列表元素逐条翻译 * 3. 单个对象:直接进行字段翻译 * 基本数据类型和null值不做处理。 *

* @param result ApiRest统一响应对象 */ private void parseFullDictText(Object result) { try { // 从ApiRest中提取实际业务数据 Object rest = ((ApiRest) result).getData(); // null值或基本数据类型(String、Integer等)无需字典翻译,直接跳过 if (rest == null || this.isBaseType(rest.getClass())) { return; } // 处理分页查询结果:遍历分页记录(records)逐条翻译字典字段 if (rest instanceof IPage) { List items = new ArrayList<>(16); for (Object record : ((IPage) rest).getRecords()) { Object item = this.parseObject(record); items.add(item); } // 用翻译后的记录替换原始分页记录 ((IPage) rest).setRecords(items); return; } // 处理列表查询结果:遍历列表中每个元素进行字典翻译 if (rest instanceof List) { List items = new ArrayList<>(); for (Object record : ((List) rest)) { Object item = this.parseObject(record); items.add(item); } // 将翻译后的列表重新写回ApiRest的data字段 ((ApiRest) result).setData(items); return; } // 处理单个对象:直接对对象的字段进行字典翻译 Object item = this.parseObject(((ApiRest) result).getData()); ((ApiRest) result).setData(item); } catch (Exception e) { e.printStackTrace(); } } /** * 处理单个对象的字典翻译和日期格式化 *

* 处理流程: * 1. 将对象序列化为JSON再解析为JSONObject,便于动态添加翻译字段 * 2. 遍历对象所有字段,按类型分别处理: * - List类型字段:递归调用processList处理嵌套列表 * - 带@Dict注解的字段:查询字典服务翻译编码为可读文本,结果以"字段名_dictText"存储 * - Date类型字段:按@JsonFormat注解指定的格式或默认格式(yyyy-MM-dd HH:mm:ss)格式化 *

* @param record 待处理的业务对象 * @return 处理后的JSONObject(包含字典翻译字段和格式化日期),null输入则返回null */ public Object parseObject(Object record) { if (record == null) { return null; } // 基本数据类型(String、Number等)没有复杂字段,无需翻译处理 if (this.isBaseType(record.getClass())) { return record; } // 将对象转为JSON格式处理,便于动态添加字典翻译字段(如xxx_dictText) String json = JSON.toJSONString(record); JSONObject item = JSONObject.parseObject(json); // 遍历对象的所有字段(包括父类字段),逐个进行翻译或格式化处理 for (Field field : Reflections.getAllFields(record)) { // 分支1:List类型字段 - 递归处理嵌套列表中的字典翻译 if (List.class.isAssignableFrom(field.getType())) { try { List list = this.processList(field, item.getObject(field.getName(), List.class)); item.put(field.getName(), list); continue; } catch (Exception e) { e.printStackTrace(); } continue; } // 分支2:带@Dict注解的字段 - 从字典表查询编码对应的可读文本 if (field.getAnnotation(Dict.class) != null) { String code = field.getAnnotation(Dict.class).dicCode(); // 字典编码字段名 String text = field.getAnnotation(Dict.class).dicText(); // 字典文本字段名 String table = field.getAnnotation(Dict.class).dictTable(); // 字典表名 String key = String.valueOf(item.get(field.getName())); // 当前字段的字典编码值 // 调用字典服务将编码值翻译为可读文本 String textValue = this.translateDictValue(code, text, table, key); if (StringUtils.isEmpty(textValue)) { textValue = ""; } // 翻译结果以"字段名_dictText"的命名规则追加到JSON对象中 item.put(field.getName() + "_dictText", textValue); continue; } // 分支3:Date类型字段 - 按注解格式或默认格式进行日期格式化 if (field.getType().getName().equals("java.util.Date") && item.get(field.getName()) != null) { // 获取字段上的@JsonFormat注解以确定日期格式 JsonFormat ann = field.getAnnotation(JsonFormat.class); // 日期格式化器 SimpleDateFormat fmt; // 优先使用@JsonFormat注解中指定的日期格式 if (ann != null && !StringUtils.isEmpty(ann.pattern())) { fmt = new SimpleDateFormat(ann.pattern()); } else { // 未指定格式时使用默认时间样式 fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } // 将时间戳(Long)格式化为日期字符串 item.put(field.getName(), fmt.format(new Date((Long) item.get(field.getName())))); continue; } } return item; } /** * 处理List类型字段的字典翻译 *

* 通过反射获取List的泛型参数确定元素实际类型, * 然后将每个列表元素反序列化为实际类型后递归调用parseObject进行字典翻译。 * 基本数据类型的列表(如List<String>)无需处理直接返回。 *

* @param field List类型的字段对象,用于获取泛型类型信息 * @param list 字段对应的列表数据 * @return 经过字典翻译处理的列表,若输入为空则返回空列表 */ private List processList(Field field, List list) { // 空列表或null直接返回空集合,避免后续空指针 if (list == null || list.size() == 0) { return new ArrayList<>(); } // 通过泛型反射获取List元素的实际数据类型(如List中的User类) Type genericType = field.getGenericType(); Class actualType = null; if (genericType instanceof ParameterizedType) { // 从参数化类型中提取第一个泛型参数作为实际类型 ParameterizedType pt = (ParameterizedType) genericType; try { actualType = (Class) pt.getActualTypeArguments()[0]; }catch (Exception e){ // 泛型类型解析失败,返回原始列表不做处理 return list; } } // 元素为基本数据类型(如String、Integer等)的列表无需字典翻译 if (isBaseType(actualType)) { return list; } // 对复杂对象列表逐条进行字典翻译处理 List result = new ArrayList<>(16); for (int i = 0; i < list.size(); i++) { // 将每个元素先转为JSON再反序列化为实际类型,确保类型准确 Object data = list.get(i); try { data = JSON.parseObject(JSON.toJSONString(data), actualType); }catch (Exception e){ // 反序列化失败时使用原始数据继续处理 } // 递归调用parseObject对列表中每个对象进行字典翻译 Object pds = this.parseObject(data); result.add(pds); } return result; } /** * 字典翻译实现:根据字典表、编码字段、文本字段和key值查询对应的可读文本 *

* 通过SysDictService查询指定字典表中,匹配code字段值为key的记录, * 返回其text字段的值作为翻译结果。 *

* @param code 字典编码字段名(如"code") * @param text 字典文本字段名(如"name"),即需要返回的翻译结果字段 * @param table 字典表名(如"sys_dict") * @param key 当前字段的字典编码值,用于匹配查询 * @return 字典翻译后的可读文本,查询失败或未找到时返回空字符串 */ private String translateDictValue(String code, String text, String table, String key) { // key为空时无需查询,直接返回null if (StringUtils.isEmpty(key)) { return null; } try { // 调用字典服务查询翻译文本 String dictText = null; if (!StringUtils.isEmpty(table)) { // 从指定字典表中,根据code字段匹配key值,返回text字段的翻译结果 dictText = sysDictService.findDict(table, text, code, key.trim()); } if (!StringUtils.isEmpty(dictText)) { return dictText; } } catch (Exception e) { // 字典查询异常时不影响主流程,打印堆栈后返回空字符串 e.printStackTrace(); } return ""; } /** * 判断给定类型是否为基本类型(无需字典翻译的简单类型) *

* 基本类型包括:8种包装类型(Integer/Byte/Long/Double/Float/Character/Short/Boolean)、 * String类型以及Number类型。这些类型不包含@Dict注解字段,无需进行字典翻译处理。 *

* @param clazz 待判断的类 * @return true表示是基本类型无需翻译,false表示是复杂对象需要翻译 */ private boolean isBaseType(Class clazz) { // 基础数据类型 if (clazz.equals(java.lang.Integer.class) || clazz.equals(java.lang.Byte.class) || clazz.equals(java.lang.Long.class) || clazz.equals(java.lang.Double.class) || clazz.equals(java.lang.Float.class) || clazz.equals(java.lang.Character.class) || clazz.equals(java.lang.Short.class) || clazz.equals(java.lang.Boolean.class)) { return true; } // String类型 if (clazz.equals(java.lang.String.class)) { return true; } // 数字 if (clazz.equals(java.lang.Number.class)) { return true; } return false; } }