在项目中使用切面注解做数据脱敏时,导出的数据也需要脱敏处理,遇到了在一个类里面调用本类的方法切面失效,解决方法如下:
切面注解:
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> response = chargingSubsidiesService.queryChargingSubsidies(reqDto);
if (response.isSuccess() && Optional.ofNullable(response.getData()).isPresent()) {
PageResult pageResult = response.getData();
ChargeSubsidyServiceImpl proxyObj = SpringContextUtil.getBean(ChargeSubsidyServiceImpl.class);
List voList = proxyObj.getExcelData(pageResult, reqDto);
String excelTitle = StringUtils.isEmpty(chargingSubsidiesReq.getExcelTitle()) ? DriverEnum.DRIVER_CHARGE_SUBSIDIES_EXCEL_TITLE.getMsg() : chargingSubsidiesReq.getExcelTitle();
List headList = new ArrayList<>();
CommonUtil.setHeadList(ChargingSubsidiesResVo.class, headList);
ExcelUtil.downloadExcelFile(excelTitle, headList, voList, servletResponse);
}
}
@EncryptMethod
public List getExcelData(PageResult pageResult,ChargingSubsidiesReqDto reqDto){
ThreadPoolExecutor poolExecutor = BussinessThreadPool.getThreadPoolExecutor();
int threadCount = CommonUtil.getThreadCount(pageResult);
List>> futureList = new ArrayList<>();
//多线程查询
for (int i = 1; i <= threadCount; i++) {
ChargingSubsidiesReqDto dto = ObjectCheckUtil.createClass(ChargingSubsidiesReqDto.class);
BeanUtils.copyProperties(reqDto,dto);
Future> submit = poolExecutor.submit(new ChargingSubsidiesTask(i, ValidateConstant.EXCEL_EXPORT_DEFAULT_SIZE, dto, chargingSubsidiesService));
futureList.add(submit);
}
List voList = new ArrayList<>();
List tempList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(futureList)) {
for (Future> future : futureList) {
try {
List 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;
@Override
public 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 代理对象
* @return
*/
public static T getBean(Class tClass) {
return applicationContext.getBean(tClass);
}
/**
* 根据传入获取真实对象
* @param beanInstance
* @return
*/
public static 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 voList = proxyObj.getExcelData(pageResult, reqDto);
我们可以debug调试下:
可以看到获取到了本类的代理对象,再看下用this调用也就是不获取代理对象的情况:
可以看到this是真实对象
这里看到的代理对象是cglib的,原因是我没有定义接口,所以spring使用的是cglib代理的
好了,完美解决了,aop切面失效的原因