序言:

在开发过程中因为功能的特殊性  需要对客户信息的手机号进行加密处理 然后存到数据库中,然而这个需求是项目中期加入,很多功能上已经使用了获取客户信息的方法,没法统一控制手机号加解密操作, 于是考虑使用 aop做环绕增强 对所有出参进行,解密操作(这里只对)。

Spring AOP--@Around--使用/实例 使用或理解上的问题

tips: 这里不赘述 注解的含义等基本概念,是以使用@Around为主的新手教程

需要引入jar包

<!-- #Spring中的切面依赖 -->
	 <dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-aop</artifactId>
	</dependency>

对于aop 网上有很多的代码示例,但是有些看不明白、写得比较含糊的问题列一下

问题1 @Around中的方法名称怎么定义:

@Around("test()")  这个中的  test()方法是自己定义的切面的方法 必须要通过@Pointcut指定切面才能生效

// 指定全局的切面
	@Pointcut("execution (* com.dealer.api.controller.*.*(..))")
	public void test() {

	}


// 环绕增强
	@Around("test()")
	public Object around(ProceedingJoinPoint proceedingJoinPoint) {
		Object proceed = null;
		String className = proceedingJoinPoint.getSignature().getDeclaringTypeName();
		String methodName = proceedingJoinPoint.getSignature().getName();
		System.out.println("============== " + className + " 类中的方法:" + methodName + " 开始执行 ==============");
		MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
		// 参数名称
		String[] parameterNames = methodSignature.getParameterNames();
		// 参数值
		Object[] args = proceedingJoinPoint.getArgs();
		for (int i = 0; i < args.length; i++) {
			System.out.println("第" + i + "个参数:" + "  key:" + parameterNames[i] + "  value:" + args[i]);
		}

		try {
			proceed = proceedingJoinPoint.proceed();
			// ====================== 返回==============================
			System.out.println("执行结果为:" + proceed);

		} catch (Throwable e) {
			e.printStackTrace();
			System.out.println("执行方法发生了错误:" + e.getMessage());
			System.out.println("============== " + className + " 类中的方法:" + methodName + " 执行结束 ==============");
			// return setResultError("执行方法发生了错误:"+e.getMessage());
			return "执行方法发生了错误:" + e.getMessage();
		}
		System.out.println("============== " + className + " 类中的方法:" + methodName + " 执行结束 ==============");
		return proceed;
	}
匹配规则使用技巧:

直接在业务方法上加@Around 注解定义切面 

定义切面的三种匹配示例

一:    // 指定类增强 DealerController 类名  该类下所有方法增强
    // @Around(value = "execution(* com.*..DealerController.*(..))")

二:
    // 指定方法增强  testEncrypt 方法名   所有叫testEncrypt 名字的方法增强
    // @Around(value = "execution(* com.*..*.testEncrypt(..))")

三: // DealerController 类名  该类下所有方法后缀为DealerDecryption的方法 增强
    @Around(value = "execution(* com.*..DealerController.*DealerDecryption(..))")

// 指定类增强 DealerController 类名
	// @Around(value = "execution(* com.*..DealerController.*(..))")
	// 指定方法增强  testEncrypt 方法名
	// @Around(value = "execution(* com.*..*.testEncrypt(..))")
	@Around(value = "execution(* com.*..DealerController.*DealerDecryption(..))")
	public Object ReturnDecryption(ProceedingJoinPoint proceedingJoinPoint) {
		Object proceed = null;
//		String className = proceedingJoinPoint.getSignature().getDeclaringTypeName();
//		String methodName = proceedingJoinPoint.getSignature().getName();
//		System.out.println("============== " + className + " 类中的方法:" + methodName + " 开始执行 ==============");
//		MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
//		// 参数名称
//		String[] parameterNames = methodSignature.getParameterNames();
//		// 参数值
//		Object[] args = proceedingJoinPoint.getArgs();
//		for (int i = 0; i < args.length; i++) {
//			System.out.println("第" + i + "个参数:" + "  key:" + parameterNames[i] + "  value:" + args[i]);
//		}
		
		try {
			proceed = proceedingJoinPoint.proceed();
			
			
			// ====================== 返回==============================
			System.out.println("执行结果为:" + proceed);

		} catch (Throwable e) {
			log.error("=========ReturnDecryption===========",e);
//			System.out.println("执行方法发生了错误:" + e.getMessage());
//			System.out.println("============== " + className + " 类中的方法:" + methodName + " 执行结束 ==============");
			// return setResultError("执行方法发生了错误:"+e.getMessage());
			return "执行方法发生了错误:" + e.getMessage();
		}
//		System.out.println("============== " + className + " 类中的方法:" + methodName + " 执行结束 ==============");
		return proceed;
	}

到这里切面环绕的功能基本实现了, 开始进入正文对业务参数加解密。

读取返回遇到问题:

在通过 proceed = proceedingJoinPoint.proceed(); 方法获取到了对象后 虽然可以将参数打印出来但是无法修改返回内容和结果,通过对象强转的方式甚至无法获取内容。

解决方式:

1、对打印的结果进行 json格式化操作,获取到具体业务返回的数据

2、返回结果无法修改,只能造一个一样的返回结果体返回出去

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cre.dmp.osp.common.response.RestResponse;
import com.Dealer.service.RemoteCallService;
import com.*.base.core.util.StringUtils;

/**
 * Title: WebException.java
 * Description: 环绕增强  对客户信息中手机号 做解密操作
 * 
 * @date 2023年7月31日
 */
// 开启切面注解
@Aspect
// lombok日志注解
@Slf4j
// 注入容器,统一管理
@Component
@SuppressWarnings({ "rawtypes", "unchecked" })
public class AopDecrypt {

    @Autowired
    RemoteCallService remoteCallService;
    
	private static String HANDLE_FIELD_NAME = "mobile"; // 特殊处理字段 TODO

	private static final String ENCRYPT_FLAG = "encrypt"; // 加密标识
															// (判断对值进行加密或者解密的操作)

	private static final String DECRYPT_FLAG = "decrypt"; // 解密标识(判断对值进行加密或者解密的操作)
 
	 

	// 指定类增强 DealerController 类名
	// @Around(value = "execution(* com.*..DealerController.*(..))")
	// 指定方法增强  testEncrypt 方法名
	// @Around(value = "execution(* com.*..*.testEncrypt(..))")
	@Around(value = "execution(* com.*..DealerController.*DealerDecryption(..))")
	public Object ReturnDecryption(ProceedingJoinPoint proceedingJoinPoint) {
		Object proceed = null;
//		String className = proceedingJoinPoint.getSignature().getDeclaringTypeName();
//		String methodName = proceedingJoinPoint.getSignature().getName();
//		System.out.println("============== " + className + " 类中的方法:" + methodName + " 开始执行 ==============");
//		MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
//		// 参数名称
//		String[] parameterNames = methodSignature.getParameterNames();
//		// 参数值
//		Object[] args = proceedingJoinPoint.getArgs();
//		for (int i = 0; i < args.length; i++) {
//			System.out.println("第" + i + "个参数:" + "  key:" + parameterNames[i] + "  value:" + args[i]);
//		}
		
		try {
			proceed = proceedingJoinPoint.proceed();
			
			
			ResponseEntity requestParam = (ResponseEntity) proceed;
//			成功 状态为200  
			if (requestParam.getStatusCodeValue() == 200) {
//				RestResponse body = (RestResponse)requestParam.getBody();
				String ss =  JSONObject.toJSONString(requestParam);
				 log.info("返回的结果为:"+JSONObject.toJSONString(requestParam));
				 Object object = JSONObject.parseObject(ss).get("body");
				 Object object2 = JSONObject.parseObject(object.toString()).get("obj") ;
				 
				 Object object3 = JSONObject.parseObject(object2.toString()).get("content") ;
				 if (object3 == null) {
					// 单个对象加密 
					 Object changValue = changValue(object2,DECRYPT_FLAG);
					 //  特殊处理数据
					 proceed = RestResponse.createSuccessRes(changValue);
				}else{
//					分页对象加密
					// list对象加密 
					 Object changValue = changValue(object3,DECRYPT_FLAG);
					
					 // 回填参数
					 JSONObject parseObject = JSON.parseObject(object2.toString());
					 parseObject.put("content", changValue);
					 
					 proceed =RestResponse.createSuccessRes(parseObject);
				}
			} 
			
			// ====================== 返回==============================
			System.out.println("执行结果为:" + proceed);

		} catch (Throwable e) {
			log.error("=========ReturnDecryption===========",e);
//			System.out.println("执行方法发生了错误:" + e.getMessage());
//			System.out.println("============== " + className + " 类中的方法:" + methodName + " 执行结束 ==============");
			// return setResultError("执行方法发生了错误:"+e.getMessage());
			return "执行方法发生了错误:" + e.getMessage();
		}
//		System.out.println("============== " + className + " 类中的方法:" + methodName + " 执行结束 ==============");
		return proceed;
	}

	/***
	 * 
	 * @exception 根据(map\list)类型区分对应的解析对象方法:
	 * @param _obj  内容
	 * @param flag  加密解密 标识字段
	 * @throws Exception
	 *             void
	 */
	private Object changValue(Object _obj, String flag) throws Exception {
		// 基本类型不作操作
		if (_obj instanceof Map) {
			changeMapValue(_obj, flag);
		} else if (_obj instanceof List) {
			List<Object> list = (List<Object>) _obj;
			for (Object obj : list) {
				if (obj instanceof Map) {
					changeMapValue(obj, flag);
				} else {
					changObjectValue(obj, flag);
				}
			}
		} else {
			changObjectValue(_obj, flag);
		}
		return _obj;
	}

	/**
	 * 
	 * @exception 解析map 格式的数据  找的需要加密的字段:
	 * @param _obj
	 * @param flag	加密解密 标识字段
	 * @return Object
	 * @throws Exception
	 */
	private Object changeMapValue(Object _obj, String flag) throws Exception {
		Map<String, Object> map = (Map<String, Object>) _obj;
		if (map.containsKey(HANDLE_FIELD_NAME)) {
			Object fieldValue = map.get(HANDLE_FIELD_NAME);
			String afterValue = crypto(fieldValue, flag);
			if (StringUtils.isNotBlank(afterValue)) {
				map.put(HANDLE_FIELD_NAME, afterValue);
			}
		}
		return _obj;
	}

	/***
	 * 
	 * @exception 解析对象内容 找到需要加密字段:
	 * @param _obj
	 * @param flag	 加密解密 标识字段
	 * @return
	 * @throws Exception  Object
	 */
	private Object changObjectValue(Object _obj, String flag) throws Exception {
		Class<?> resultClz = _obj.getClass();
		Field[] fieldInfo = resultClz.getDeclaredFields(); // 获取class里的所有字段
															// 父类字段获取不到
															// 注:如果出现加密解密失败
															// 请先查看mobile是否在父类中
		for (Field field : fieldInfo) {
			if (HANDLE_FIELD_NAME.equals(field.getName())) {
				field.setAccessible(true); // 成员变量为private,故必须进行此操
				Object fieldValue = field.get(_obj);
				String afterValue = crypto(fieldValue, flag);
				if (StringUtils.isNoneBlank(afterValue)) {
					field.set(_obj, afterValue);
				}else{
					
				}
				break;
			}
		}
		return _obj;
	}
	
    
	/**
	 * 对特殊字段 进行加解密
	 * @param value  加密内容
	 * @param flag	加解密方式
	 * @return
	 * @throws Exception
	 */
	private String crypto(Object value, String flag) throws Exception {
		if (value == null) {
			return null;
		}
		// 加密操作;加密之前先去查询一下数据库 有没有 如果没有 则insert
		if (ENCRYPT_FLAG.equals(flag)) {
			List  e1 = new ArrayList();
	       	e1.add(value);
	   		// 加密
	       	Map<String, String> encrypt = remoteCallService.encrypt(e1);
	       	return encrypt.get(value);
		} else { // 解密操作 通过seq 查询 然后解密返回明文
	       	List  e11 = new ArrayList();
	       	e11.add(value);
	        	// 解密
	   		Map<String, String> decrypt = remoteCallService.decrypt(e11);
	   		
	   		String mobile = decrypt.get(value);
	   		String result = ""; 
	   		if (StringUtils.isNoneBlank(mobile)) {
	   			  result = mobile.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
			}else{
				// 解密失败时  屏蔽手机号
				mobile = value.toString();
				if (mobile.length()==11) {
					result = mobile.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
				}else{
					result =mobile;
				}
			}
	   		
	   		return result;
		}
	}

}