1、需求

调用微信接口之前:统一权限校验及异常处理。

2、思路

  • 提供自定义注解,可配置需要的参数
  • aop拦截自定义注解,方法执行前校验权限,拦截并过滤方法抛出的异常

3、核心代码

3.1、自定义注解@WxFuncAuth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WxFuncAuth {
/**
* 方法权限列表
*
* @return 方法权限列表
*/
String[] funcs() default {};

/**
* 微信认证状态,默认:认证
* 有些接口不需要微信认证即可调用
*
* @return 微信认证状态
*/
boolean isVerify() default true;

/**
* appId
*
* @return appId
*/
String appId() default "";
}

3.2、微信方法权限注解拦截WxFuncAuthAspect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Aspect
@Component
@Slf4j
public class WxFuncAuthAspect {
/**
* 用于SpEL表达式解析.
*/
private final SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private final ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

@AfterThrowing(throwing = "ex", value = "@annotation(wxFuncAuth)")
public void wxFuncAuth(JoinPoint joinPoint, Throwable ex, WxFuncAuth wxFuncAuth) {
//获取appId
String appId = generateKeyBySpringEL(wxFuncAuth.appId(), joinPoint);
log.info("After throwing appId:{},message:{}", appId, ex.getMessage());
if (ex instanceof WxServiceException) {
//微信返回的48001异常
if (((WxServiceException) ex).getErrorCode() == WxMaErrorMsgEnum.CODE_48001.getCode()) {
//自定义处理
}
}
}

@Before(value = "@annotation(wxFuncAuth)")
public void wxFuncAuth(JoinPoint joinPoint, WxFuncAuth wxFuncAuth) {
//获取appId
String appId = generateKeyBySpringEL(wxFuncAuth.appId(), joinPoint);
log.info("annotation WxFuncAuth Before appId:{}", appId);
if (StringUtils.isBlank(appId)) {
throw new WxServiceException("appId不存在");
}
log.info("annotation WxFuncAuth Before funcs:{}", JSON.toJSONString(wxFuncAuth.funcs()));

//配置了接口权限集,才需要校验
if (wxFuncAuth.funcs().length > 0) {
//校验微信权限信息
}

}

/**
* 根据el表达式字符串生成key
*
* @param elString el表达式字符串
* @param joinPoint joinPoint
* @return key
*/
private String generateKeyBySpringEL(String elString, JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), AopUtils.getTargetClass(joinPoint.getTarget()));
Expression expression = parser.parseExpression(elString);
EvaluationContext context = new MethodBasedEvaluationContext(new Object(), targetMethod, joinPoint.getArgs(), nameDiscoverer);
return Objects.requireNonNull(expression.getValue(context)).toString();
}
}

3.3、测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class WxFuncAuthAnnotationCaller {
/***
* 正常场景测试
* @param appId appId
* @return String
*/
@WxFuncAuth(appId = "#appId", funcs = {"1", "2"})
public String callNormal(String appId) {
return appId + ": I'm very good";
}

/***
* 异常场景测试
* @param appId appId
*/
@WxFuncAuth(appId = "#appId", funcs = {"1", "2"})
public void callError(String appId) {
throw new WxServiceException(appId+"权限不足",48001);
}
}

4、总结

通过@Cacheable中Spring EL表达式的借鉴完成了这个需求,重点关注WxFuncAuthAspect类中的generateKeyBySpringEL方法:

  • 通过org.springframework.aop.support.AopUtils#getMostSpecificMethod获取方法对象,这块曾经有个坑。
  • TemplateAwareExpressionParser#parseExpression解析SpEL表达式
  • 再通过MethodBasedEvaluationContext和DefaultParameterNameDiscoverer配合,从方法中获取参数