代理模式。
需求背景返回前端的用户信息包含手机号等敏感内容,并在系统上已经有大量接口。现拿到需求,需要在返回前端的对象中,找到手机号的属性并将手机号加密。
现状:
分散在各处的接口中都有手机号需要加密。
不同对象里对手机号命名可能不同,如 String number ; String phone。
思路:
按接口逐个添加加密逻辑过于繁琐,不利后续维护。
考虑创建一个Util类但不好兼容不同用户信息对象的类型,且属性名称也不统一。
考虑使用自定义注解+代理模式,即AOP思想
@Target(ElementType.METHOD) // 定义注解的作用目标**作用范围字段、枚举的常量/方法
@Retention(RetentionPolicy.RUNTIME) // RUNTIME:运行时可以获取相关属性。
@documented // 说明该注解将被包含在javadoc中
public @interface PhoneAccess {
String phoneFiled() default "phone";
String phoneResource() default "phoneAccessBtn";
}
自定义注解创建后,虽然可以在系统任意方法上使用@PhoneAccess,但没有具体意义。
创建Aspect
- 使用@Aspect标记这是一个Aspect类
- 使用@Around("@annotation(pa)") 对注解刚刚创建的自定义注解:@PhoneAccess 环绕通知
- 方法需要传入 ProceedingJoinPoint point, PhoneAccess pa 。
- 利用 point.proceed(); 先执行被代理的目标函数
- PhoneAccess pa 将被代理的目标函数上的对象信息传入,再做加密
@Aspect // 使用@Aspect标记这是一个Aspect
@Component
public class PhoneAspect {
private final Logger logger = LoggerFactory.getLogger(PhoneAspect.class);
private static ObjectMapper mapper = new ObjectMapper();
@Around("@annotation(pa)") // 对注解:@PhoneAccess 环绕通知
public Object serviceLog(ProceedingJoinPoint point, PhoneAccess pa) throws Throwable {
Object result = point.proceed(); // 执行目标函数
// 代理的内容
if(ObjectUtils.isEmpty(result)){
return result;
}
if(ObjectUtils.isEmpty(pa)){
return result;
}
Map map = mapper.readValue(mapper.writevalueAsString(result), Map.class);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession s = request.getSession();
if(ObjectUtils.isEmpty(s)){
return result;
}
Object o = s.getAttribute(UnifiedProfileConstants.UNIFIED_PROFILE);
UnifiedProfile unifiedProfile = (UnifiedProfile)o;
List buttonResourceList = unifiedProfile.getButtonList();
if(ObjectUtils.isEmpty(buttonResourceList)){
return result;
}
//判断是否资源权限
Boolean hasAccess = hasPhoneAccess(buttonResourceList, pa.phoneResource());
if(hasAccess){
return result;
}
//遮盖电话号码
recursiveAnalysis(map, pa.phoneFiled(), hasAccess);
Object newResult = null;
try {
newResult = mapper.readValue(mapper.writevalueAsString(map), result.getClass());
} catch (Exception e) {
e.printStackTrace();
return result;
}
return newResult;
}
private Boolean hasPhoneAccess(List buttonResources, String phoneResource) {
Boolean hasAccess = Boolean.FALSE;
if(!ObjectUtils.isEmpty(buttonResources)){
for(ButtonResource br: buttonResources){
if(br.getKey().equals(phoneResource)){
hasAccess = br.getHasAccess();
}
}
}
return hasAccess;
}
public static void recursiveAnalysis(Object m, String fieldName, Boolean hasAccess){
Map mp = null;
if(m instanceof Map || m instanceof linkedHashMap){
mp = (linkedHashMap)m;
for(Iterator ite = mp.entrySet().iterator(); ite.hasNext();){
Map.Entry e = (Map.Entry) ite.next();
if(e.getValue() instanceof String){
if(!ObjectUtils.isEmpty(e.getValue()) && e.getValue().toString().length() == 11){
if(!hasAccess && e.getKey().equals(fieldName)){
String str = "****";
StringBuilder sb = new StringBuilder(e.getValue().toString());
sb.replace(3, 7, str);
e.setValue(sb.toString());
}
}
}else if(e.getValue() instanceof linkedHashMap){
recursiveAnalysis((linkedHashMap)e.getValue(), fieldName, hasAccess);
}else if(e.getValue() instanceof ArrayList){
recursiveAnalysis((ArrayList)e.getValue(), fieldName, hasAccess);
}
}
}
List ls = null;
if(m instanceof List || m instanceof ArrayList){
ls = (ArrayList)m;
for(int i=0;i
自此
可以在任意需要加密处理的接口打上注解@PhoneAccess,指定当前返回值中存放手机号的属性名,就能完成加密。



