在项目中使用切面注解做数据脱敏时,导出的数据也需要脱敏处理,遇到了在一个类里面调用本类的方法切面失效,解决方法如下:
切面注解:
package com.t3.ts.driver.resume.aspect;import java.lang.annotation.*;/*** @Description: 数据脱敏注解 Filed* @Date: 2019/9/10* @Author: wm yu*/
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
package com.t3.ts.driver.resume.aspect;import java.lang.annotation.*;/*** @Description: 数据脱敏注解 Method* @Date: 2019/9/10* @Author: wm yu*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptMethod {
}
切面类:
package com.t3.ts.driver.resume.aspect;import com.alibaba.fastjson.JSON;
import com.t3.ts.driver.resume.utils.MD5Util;
import com.t3.ts.driver.resume.utils.StringUtils;
import com.t3.ts.driver.resume.utils.excel.FieldReflectionUtil;
import com.t3.ts.result.PageResult;
import com.t3.ts.result.Response;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;/*** @class_name: DecryptAspect* @description:* @author: wm_yu* @create: 2019/09/10**/
@Aspect
@Component
@Order(-1)
public class EncryptAspect {private final static Logger log = LoggerFactory.getLogger(EncryptAspect.class);/*** 身份证脱敏正则 保留前后四位*/private final static String IDENTITY_CARD_DESENSITIZATION = "(?<=\\d{4})\\d(?=\\d{4})";/***银行卡脱敏正则 保留前后四位*/private final static String BLANK_CARD_DESENSITIZATION = "(?<=\\d{4})\\d(?=\\d{4})";/*** 手机号脱敏正则 保留前三后四位*/private final static String MOBILE_DESENSITIZATION = "(?<=\\d{3})\\d(?=\\d{4})";/*** 定义其他字段脱敏长度*/private final static Integer OTHER_DESENSITIZATION_LENGTH = 3;private final static Integer IDENTITY_CARD_LENGTH_18 = 18;private final static Integer IDENTITY_CARD_LENGTH_15 = 15;private final static Integer MOBILE_LENGTH = 11;@Pointcut("@annotation(com.t3.ts.driver.resume.aspect.EncryptMethod)")public void pointCut(){}/*** 注明切点*/@Around("pointCut()")public Object around(ProceedingJoinPoint joinPoint){Object responseObj = null;try {responseObj = joinPoint.proceed();//数据脱敏handleEncrypt(responseObj);} catch (Throwable throwable) {log.error("数据脱敏异常:{}", JSON.toJSONString(responseObj),throwable);}return responseObj;}/*** 处理加密** @param responseObj*/private void handleEncrypt(Object responseObj) throws IllegalAccessException {if (!Optional.ofNullable(responseObj).isPresent()) {return;}Object var = null;if(responseObj instanceof List){var = responseObj;}else{Response response = (Response) responseObj;var = response.getData();}if(!Optional.ofNullable(var).isPresent()){return;}this.dealDateByType(var);}/*** 类型判断处理* @param var* @throws IllegalAccessException*/private void dealDateByType(Object var) throws IllegalAccessException {Field[] fields = {};if(var instanceof PageResult){//分页列表数据PageResult pageResult = (PageResult) var;List list = pageResult.getList();List filterList = (List) list.stream().filter(x -> Optional.ofNullable(x).isPresent()).collect(Collectors.toList());for (Object o : filterList) {fields = FieldReflectionUtil.getAllFields(o.getClass());dealPrecisionField(fields,o);}}if(var instanceof List){List list = (List) var;List filterList = (List) list.stream().filter(x -> Optional.ofNullable(x).isPresent()).collect(Collectors.toList());for (Object o : filterList) {fields = FieldReflectionUtil.getAllFields(o.getClass());dealPrecisionField(fields,o);}}else{//详情页面等 --- 数据加密处理,需要前端配合解密展示fields = FieldReflectionUtil.getAllFields(var.getClass());dealEncryptField(fields,var);}}/*** 处理数据加密 前端配合解密处理* @param fields* @param var*/private void dealEncryptField(Field[] fields,Object var) throws IllegalAccessException {for (Field field : fields) {if(!Optional.ofNullable(field).isPresent() || !field.isAnnotationPresent(EncryptField.class)){continue;}if(!field.isAccessible()){field.setAccessible(true);}Object o = field.get(var);if(!Optional.ofNullable(o).isPresent()){continue;}if(!(o instanceof String)){//递归处理Field[] allFields = FieldReflectionUtil.getAllFields(o.getClass());this.dealEncryptField(allFields,o);}else{String value = encryptField((String) o);field.set(var,value);}}}/*** 字段加密处理* @return*/private String encryptField(String source){if(StringUtils.isEmpty(source)){return source;}String encryptValue = MD5Util.MD5Encode(source).toUpperCase();return encryptValue;}/*** 处理数据脱敏* @param fields* @param var* @throws IllegalAccessException*/private void dealPrecisionField(Field[] fields,Object var) throws IllegalAccessException {for (Field field : fields) {if(!Optional.ofNullable(field).isPresent() || !field.isAnnotationPresent(EncryptField.class)){continue;}if(!field.isAccessible()){field.setAccessible(true);}Object o = field.get(var);if(!Optional.ofNullable(o).isPresent()){continue;}if(!(o instanceof String) && !(o instanceof Integer)){//递归处理Field[] allFields = FieldReflectionUtil.getAllFields(o.getClass());this.dealPrecisionField(allFields,o);}else{Object value = null;if(o instanceof String){value = dealFieldValue(o);}if(o instanceof Integer){value = dealFieldValue( o);}field.set(var,value);}}}/*** 字段数据脱敏* @param obj* @return*/private Object dealFieldValue(Object obj){//integer类型枚举的直接返回if(obj instanceof Integer){return null;}String value = (String) obj;if(StringUtils.isEmpty(value)){return value;}if(value.length() == IDENTITY_CARD_LENGTH_18 || value.length() == IDENTITY_CARD_LENGTH_15){value = idCardReplace(value);}if(value.length() == MOBILE_LENGTH){value = mobileReplace(value);}if(value.length() <= OTHER_DESENSITIZATION_LENGTH){value = dealLessField(value);}return value;}private String dealLessField(String value){if(StringUtils.isEmpty(value) || value.length() > 3){return value;}StringBuilder builder = new StringBuilder();for (int i = 0; i < value.length(); i++) {builder.append("*");}return builder.toString();}/*** 身份证号脱敏,保留前四位和后四位* @param idCard 身份证号* @return*/public String idCardReplace(String idCard) {if (StringUtils.isEmpty(idCard)) {return null;}return replaceAction(idCard, IDENTITY_CARD_DESENSITIZATION);}/*** 银行卡替换,保留后四位* @param bankCard 银行卡号* @return*/public String bankCardReplace(String bankCard) {if (StringUtils.isEmpty(bankCard)) {return null;}return replaceAction(bankCard, BLANK_CARD_DESENSITIZATION);}/***手机号脱敏,保留前三后四位* @param mobile* @return*/public String mobileReplace(String mobile){if(StringUtils.isEmpty(mobile)){return mobile;}return replaceAction(mobile,MOBILE_DESENSITIZATION);}/*** 脱敏操作* @param source* @param regular 正则* @return*/private String replaceAction(String source, String regular) {return source.replaceAll(regular, "*");}}
业务调用的方法:
public void export(ChargingSubsidiesReq chargingSubsidiesReq, HttpServletResponse servletResponse) {if (null == chargingSubsidiesReq) {chargingSubsidiesReq = new ChargingSubsidiesReq();}chargingSubsidiesReq.setPageSize(1);chargingSubsidiesReq.setCurrPage(1);ChargingSubsidiesReqDto reqDto = ObjectCheckUtil.createClass(ChargingSubsidiesReqDto.class);this.setCondition(chargingSubsidiesReq,reqDto);Response<PageResult<ChargingSubsidiesResDto>> response = chargingSubsidiesService.queryChargingSubsidies(reqDto);if (response.isSuccess() && Optional.ofNullable(response.getData()).isPresent()) {PageResult<ChargingSubsidiesResDto> pageResult = response.getData();ChargeSubsidyServiceImpl proxyObj = SpringContextUtil.getBean(ChargeSubsidyServiceImpl.class);List<ChargingSubsidiesResVo> voList = proxyObj.getExcelData(pageResult, reqDto);String excelTitle = StringUtils.isEmpty(chargingSubsidiesReq.getExcelTitle()) ? DriverEnum.DRIVER_CHARGE_SUBSIDIES_EXCEL_TITLE.getMsg() : chargingSubsidiesReq.getExcelTitle();List<String> headList = new ArrayList<>();CommonUtil.setHeadList(ChargingSubsidiesResVo.class, headList);ExcelUtil.downloadExcelFile(excelTitle, headList, voList, servletResponse);}}@EncryptMethodpublic List<ChargingSubsidiesResVo> getExcelData(PageResult pageResult,ChargingSubsidiesReqDto reqDto){ThreadPoolExecutor poolExecutor = BussinessThreadPool.getThreadPoolExecutor();int threadCount = CommonUtil.getThreadCount(pageResult);List<Future<List<ChargingSubsidiesResDto>>> futureList = new ArrayList<>();//多线程查询for (int i = 1; i <= threadCount; i++) {ChargingSubsidiesReqDto dto = ObjectCheckUtil.createClass(ChargingSubsidiesReqDto.class);BeanUtils.copyProperties(reqDto,dto);Future<List<ChargingSubsidiesResDto>> submit = poolExecutor.submit(new ChargingSubsidiesTask(i, ValidateConstant.EXCEL_EXPORT_DEFAULT_SIZE, dto, chargingSubsidiesService));futureList.add(submit);}List<ChargingSubsidiesResVo> voList = new ArrayList<>();List<ChargingSubsidiesResDto> tempList = new ArrayList<>();if (CollectionUtils.isNotEmpty(futureList)) {for (Future<List<ChargingSubsidiesResDto>> future : futureList) {try {List<ChargingSubsidiesResDto> dtoList = future.get();tempList.addAll(dtoList);} catch (InterruptedException | ExecutionException e) {future.cancel(true);log.error("获取线程数据异常{}:", e.getMessage(), e);}}tempList.stream().forEach(var -> {ChargingSubsidiesResVo vo = ObjectCheckUtil.createClass(ChargingSubsidiesResVo.class);BeanUtils.copyProperties(var, vo);vo.setIdentityCard(var.getIdNumber());vo.setMobile(var.getDriverMobile());voList.add(vo);});}return voList;}
该业务是使用多线程获取excel导出的数据,在再使用多线程填充excel,具体见我的另外一篇博客:
https://blog.csdn.net/qq_42151769/article/details/100674862
如果你在导出中使用:
this.getExcelData()的方法,那么不好意思,切面是无效的,原因是this是真实对象,不是一个代理对象,而aop切面必须是代理对象才能生效的,那么,我们就需要想办法获取到代理对象,因为spring IOC中存放的就是代理类对象,所以我们需要拿到它
如下: 注意别忘记了打上注解@Component
package com.t3.ts.driver.resume.context;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;/*** @class_name: SpringContextUtil* @description: 获取spring ioc中的bean* @author: wm_yu* @create: 2019/09/12**/
@Component
public class SpringContextUtil implements ApplicationContextAware {private final static Logger log = LoggerFactory.getLogger(SpringContextUtil.class);private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContextParam) throws BeansException {applicationContext = applicationContextParam;}public static Object getObject(String id) {Object object = null;object = applicationContext.getBean(id);return object;}/*** 类路径获取* @param tClass* @return*/public static Object getBean(String tClass) {return applicationContext.getBean(tClass);}/*** 字节码对象获取* @param tClass* @param <T> 代理对象* @return*/public static <T> T getBean(Class<T> tClass) {return applicationContext.getBean(tClass);}/*** 根据传入获取真实对象* @param beanInstance* @return*/public static<T> T getTarget(T beanInstance) {if (!AopUtils.isAopProxy(beanInstance)) {return beanInstance;} else if (AopUtils.isCglibProxy(beanInstance)) {try {Field h = beanInstance.getClass().getDeclaredField("CGLIB$CALLBACK_0");h.setAccessible(true);Object dynamicAdvisedInterceptor = h.get(beanInstance);Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");advised.setAccessible(true);T target = (T)((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();return target;} catch (Exception e) {log.error("获取真实对象异常:{}",e.getMessage(),e);}}return null;}}
通过这个类,我们就能拿到代理对象了:
在代码中引用他:
ChargeSubsidyServiceImpl proxyObj = SpringContextUtil.getBean(ChargeSubsidyServiceImpl.class);List<ChargingSubsidiesResVo> voList = proxyObj.getExcelData(pageResult, reqDto);
我们可以debug调试下:
可以看到获取到了本类的代理对象,再看下用this调用也就是不获取代理对象的情况:
可以看到this是真实对象
这里看到的代理对象是cglib的,原因是我没有定义接口,所以spring使用的是cglib代理的
好了,完美解决了,aop切面失效的原因