自定义注解+AOP实现字典值的翻译

20 篇文章 0 订阅
6 篇文章 0 订阅

前言

字典部分这一块无论是前端和后端都可以做,在目前所接触的项目环境中是前端来做会比较方便的,但是有遇到需要不断远程调用查询的场景,类似的动作不断重复的代码,因此就自己写了个注解实现字典翻译,故此来记录一下过程。

环境:SpringBoot+Mybatis-plus

分析过程

很多场景下,我们存储的数据是需要经过翻译的,就比如:

  • 1=男;0=女
  • 1=已启用;2=已停用

这些数据存在系统的字典中,表数据存储的时候为了性能考虑不直接存 男 、 女,只是存1 或 0 ,前台展示的时候就需要把该值进行翻译。

每个业务都写的话会比较多类似的操作,可以使用AOP的形式简化工作。

问题一

一般我们存在男女结构的字段可能是这样的:

	/**
	 * 性别
	 */
	private Integer sex;

是一个Int类型的数据,而翻译过后的数据是String类型的, 我们可以手动添加String类型字段:

	/**
	 * 性别
	 */
	private Integer sex;

	/**
	 * 性别
	 */
	private String sexText;

那有大量的操作我们都需要手动添加字段,貌似也有点麻烦。

解决办法:

注解应该放在controller上,在controller执行完后才对数据增强做翻译工作,因前后端分离返回给前端的是JSON,可以用JSONObject对象

那么JSONObject是怎么做的呢?

//使用put方法增加一个key、value
jsonObject.put(fieldName + SUFFIX, value);

这里的fieldName可以理解为sex,SUFFIX是后缀,比如统一为TEXT

那么前端展示数据的时候就可以直接使用sexText字段即可。

问题二

切点应该放在controller吗?注解怎么设计?

这里有两个注解,一个注解放在entity的成员变量上,另一个放在controller方法上,切点只要捕捉注解即可。设计的时候还是尽量偏向于要的时候再做,减少对整个系统的影响。

在entity上标注@Dict注解,注明需要翻译的字段为sex,这里的key是只字典参数的key

	/**
	 * 性别
	 */
	@Dict(fieldName = "sex", key="SYSTEM_SEX")
	private Integer sex;

在controller上标注@DoDict作为aop切面的切点

	@DoDict
	@GetMapping("/page")
	public R page() {
        //...执行操作
    }

其中@Dict注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {

	/**
	 * 需要翻译的字段名称
	 */
	String fieldName();

	/**
	 * 系统参数的key值
	 * @return paramKey
	 */
	String key();
}

其中fieldName表名是哪个字段需要翻译,key作为字典的参数key,用于查询字典时用的。

这里可能每个项目设计的有不同,因此可以灵活设计。

其中@DoDict注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoDict {
    
}

该注解作用在controller方法上,仅仅标识需要开启自动翻译。有的教程切点是作为controller的,但并不是所有场景都需要,因此增加了该注解。

其中统一返回的结果集R:

@Data
public class R<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    private int code;
    private boolean success;
    private T data;
    private String msg;
}

问题三

使用JSONObject可以添加字段,但是对象里的sex值应该怎么获取到?

主要是通过反射的形式,java的反射能够知道这个类的所有属性和方法,甚至是改变它的属性值,因此从反射中拿即可。

public class BeanUtil{
	/**
	 * 通过反射获取包括父类的所有类属性
	 *
	 * @param object obj
	 * @return 获取类的所有属性
	 */
	public static Field[] getAllFields(Object object) {
		Class<?> clazz = object.getClass();
		List<Field> fieldList = new ArrayList<>();
		while (clazz != null) {
			fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
			clazz = clazz.getSuperclass();
		}
		Field[] fields = new Field[fieldList.size()];
		fieldList.toArray(fields);
		return fields;
	}

}

通过Field对象循环遍历即可。

代码实现

AOP的代码(核心):

/**
 * 自定义切面,翻译字典字段
 *
 */
@Aspect
@Component
@Slf4j
public class DoDictAspect {
	private final String SUFFIX = "Text";


	/**
	 * 当DoDict标注时执行
	 */
	@Pointcut("@annotation(org.springblade.system.business.smsreminder.anno.DoDict)")
	public void execute() {
	}

	@Around("execute()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		long start = System.currentTimeMillis();
        //获取执行完的数据
		Object proceed = point.proceed();
		//翻译数据
		doDict(proceed);
		log.info("解析数据执行时间:{}", (System.currentTimeMillis() - start));
		return proceed;
	}

}

这里我们可以看到,我们具体执行逻辑都在环绕方法中,获取程序执行完的数据后对数据增强,具体的实现逻辑在

	Object proceed = point.proceed();
	//翻译数据
	doDict(proceed);

核心代码doDict:

	/**
	 * 翻译字典值、调用远程方法获取号码归属地
	 *
	 * @param proceed controller执行完的数据
	 */
	private void doDict(Object proceed) {
        //如果返回结果不是R的话就返回
		if (!(proceed instanceof R)) {
			return;
		}
		R<Object> r = (R) proceed;
        //获取相关数据
		Object data = r.getData();
		if (data == null) {
			return;
		}
		JSONObject pageJsonObject = null;
		JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(data));
		if (data instanceof List) {
			//当返回结果为集合时执行
			pageJsonObject = convertList(data, jsonObject);
		} else {
			//当返回结果为Mybatis-plus分页结果时
			pageJsonObject = convertPage(data, jsonObject);
		}
        //重新set数据
		r.setData(pageJsonObject);
	}

	/**
	 * 获取Mybatis-plus分页records,并将数据翻译
	 *
	 * @param data       IPage<?>
	 * @param jsonObject IPage<?> -> jsonObject
	 * @return 翻译好的数据
	 */
	private JSONObject convertPage(Object data, JSONObject jsonObject) {
		//校验
		if (!(data instanceof IPage)) {
			return jsonObject;
		}
		IPage page = (IPage) data;
		if (CollectionUtil.isEmpty(page.getRecords())) {
			return jsonObject;
		}
		//开始执行翻译字典或归属地
		List<JSONObject> result = doConvertDict(page.getRecords());
		page.setRecords(result);
		//返回整个IPage
		return JSON.parseObject(JSON.toJSONString(page));
	}

	/**
	 * 利用反射执行翻译操作
	 *
	 * @param records
	 * @return
	 */
	private List<JSONObject> doConvertDict(List<?> records) {
		List<JSONObject> result = new ArrayList<>();
		for (Object record : records) {
            //利用反射获取该对象的属性
			for (Field field : BeanUtil.getAllFields(record)) {
				if (field.getAnnotation(Dict.class) == null) {
					continue;
				}
				JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(record));
				//获取需要翻译的字段名称,比如 sex
				String fieldName = field.getAnnotation(Dict.class).fieldName();
				//获取当前需要翻译的字段的值 比如 1 
				String currentFieldName = String.valueOf(jsonObject.get(fieldName));

				//执行翻译字典的相关操作 该内容为具体的业务实现,项目不同过程不同。
				String systemParamKey = field.getAnnotation(Dict.class).key();
				String value = ParamCache.getValue(systemParamKey);
				//获取字段参数值
				String paramValues = CommonUtil.getParamValues(systemParamKey, currentFieldName);
                //put数据,并且加上后缀
				jsonObject.put(fieldName + SUFFIX, paramValues);
				result.add(jsonObject);
			}
		}
		return result;
	}

	private JSONObject convertList(Object data, JSONObject jsonObject) {
		List<?> list = (List<?>) data;
		if (CollectionUtil.isEmpty(list)) {
			return jsonObject;
		}
		//执行翻译字典或归属地
		List<JSONObject> result = doConvertDict(list);
		//返回整个IPage
		return JSON.parseObject(JSON.toJSONString(result));
	}
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要通过自定义注解AOP实现Spring Security配置指定接口不需要Token才能访问,可以按照以下步骤进行操作: 1. 创建一个自定义注解,例如`@NoTokenRequired`,用于标识不需要Token的接口。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoTokenRequired { } ``` 2. 创建一个切面类,用于拦截带有`@NoTokenRequired`注解的方法,并跳过Spring Security的Token验证。 ```java @Aspect @Component public class TokenValidationAspect { @Before("@annotation(com.example.NoTokenRequired)") public void skipTokenValidation(JoinPoint joinPoint) { // 跳过Spring Security的Token验证逻辑 SecurityContextHolder.getContext().setAuthentication(null); } } ``` 3. 配置Spring Security,将AOP切面类添加到Spring Security的配置中。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenValidationAspect tokenValidationAspect; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 配置需要Token验证的接口 .anyRequest().authenticated() .and() .csrf().disable(); // 将AOP切面类添加到Spring Security的配置中 http.addFilterBefore(tokenValidationAspect, UsernamePasswordAuthenticationFilter.class); } } ``` 4. 在需要不需要Token验证的接口上,添加`@NoTokenRequired`注解。 ```java @RestController public class ExampleController { @NoTokenRequired @GetMapping("/example") public String example() { return "This API does not require Token"; } } ``` 这样配置之后,带有`@NoTokenRequired`注解的接口将不会进行Spring Security的Token验证,即可在没有Token的情况下访问该接口。其他接口仍然需要进行Token验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值