JoinPoint用于before形切面,ProceedingJoinPoint用于环绕around形切面
signature: MethodSignature signature = (MethodSignature) jp.getSignature();
returnType: 方法返回值信息
name: 全路径返回类的名称com....Xxx
parameterTypes:
【0】:第一个参数
name: 全路径入参的名称com....Xxx
入参的值:MethodInvocation methodInvocation = (MethodInvocation) jp.getSignature();
Object[] arguments = methodInvocation.getArguments();
method: Method method = signature.getMethod();
name: 方法名称
annotation: Authentication annotation = method.getAnnotation(Authentication.class);
注解的全路径名称:Class<? extends Annotation> annotationType = annotation.annotationType();
注解中属性值
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 作用的类名
Class resp = signature.getReturnType();
String respName = resp.getName();
String respSimpleName = resp.getSimpleName();
// 作用方法名
Method method = signature.getMethod();
String methodName = method.getName();
// req名
String[] parameterNames = signature.getParameterNames();
Class[] parameterTypes = signature.getParameterTypes();
Class parameterType = parameterTypes[0];
String reqName = parameterType.getName();
String reqSimpleName = parameterType.getSimpleName();
// 方法入参req内容
Object[] args = pjp.getArgs();//方法所有入参
Object arg = args[0];//方法第一个入参对象(含有属性值)
// annocation名称和属性值
Authentication annotation = method.getAnnotation(Authentication.class);
Class<? extends Annotation> annotationType = annotation.annotationType();
String annotationTypeName = annotationType.getName();
String annotationSimpleName = annotationType.getSimpleName();
int poiType = annotation.poiType();//注解自定义的属性值
aop1
public class RdcRefundMoveShelfInterceptorManager implements ApplicationContextAware {
@Autowired RdcRefundMoveShelfInterceptorManager proxy;
public void executeRdcRefundMoveShelf(MoveShelfContext context) {
log.info("开始执行移库责任链,context:{}", GsonUtil.toJsonString(context));
log.info("refundBillNo:{},开始执行移库同步操作", context.getRefundBillNo());
// 执行同步操作
doExecuteSyncInterceptors(context);
log.info("refundBillNo:{},开始执行移库异步操作,context:{}", context.getRefundBillNo(),GsonUtil.toJsonString(context));
// 执行异步操作
proxy.doExecuteAsyncInterceptors(context); //!!!!!!这里,要使用proxy代理对象执行
}
}
aop2:
等效
((RefundOutBoundBillStatusChangeJobOptm)AopContext.currentProxy()).processRdcNotSystemBill(bill,dayLimit);
加锁
方法
解锁
1、注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RefundConcurrentControl {
/** 操作间隔时间,分布式锁场景就是锁的超时时间 */
int intervalTimeSeconds();
/** 并发控制键计算规则 */
String keyGenRule();//貌似必须要加#前缀,expression解析的时候需要
/** 是否需要释放,一般分布式锁场景需要释放 */
boolean needRelease();
/** 指定前缀;如果不指定前缀就是类名+方法名*/
String specifyPrefix() default "";
/** 指定错误提示;如果不指定就按系统默认值*/
String specifyErrorMsg() default "";
}
2、aop实现
//依赖spring-expression,这个在spring-context包下有,一般spring服务都会有这个依赖
//还依赖spring-core,一般spring服务都会有
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
@Slf4j
public class ConcurrentControlAspect implements Ordered {
private static final String CAT_TYPE = ConcurrentControlAspect.class.getSimpleName();
private SpelExpressionParser elExpressionParser = new SpelExpressionParser();
private DefaultParameterNameDiscoverer parameterNameDiscoverer =
new DefaultParameterNameDiscoverer();
@Autowired private RefundDistributeLock distributeLock;
//默认错误提示
private static final String COMMON_ERROR_MSG = "当前操作过于频繁,请稍后再试";
// 0.生效的范围是注解,around方式(加锁 - 方法 - 解锁)环绕方式
@Around(
"@annotation(com.sankuai.grocerywms.logistics.sharedrefund.annotation.RefundConcurrentControl)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 1、根据方法注解解析接口配置信息
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
RefundConcurrentControl controlConfig = method.getAnnotation(RefundConcurrentControl.class);
// 解析参数
if (Objects.isNull(controlConfig)) {
throw new BusinessException(Constants.CONCURRENT_CONTROL_CONFIG_ERROR, "参数为空");
}
// 两次操作间隔时间
int intervalTimeSeconds = controlConfig.intervalTimeSeconds();
// 生成键规则
String specifyPrefix = controlConfig.specifyPrefix();
// 生成键规则
String keyGenRule = controlConfig.keyGenRule();
// 是否需要释放
boolean needRelease = controlConfig.needRelease();
//错误提示
String errorMsg = !Strings.isBlank(controlConfig.specifyErrorMsg()) ? controlConfig.specifyErrorMsg() : COMMON_ERROR_MSG;
// 2、计算lockKey
String lockKey = generateLockKey(pjp, specifyPrefix,keyGenRule);
// 3、加锁(aop前缀增强)
boolean lockResult = false;
try {
lockResult = distributeLock.lock(lockKey, intervalTimeSeconds);
if (!lockResult) {
Cat.logEvent(CAT_TYPE, "LOCK_FAIL");
throw new BusinessException(
Constants.CONCURRENT_CONTROL_LOCK_FAIL, errorMsg);
}
// 4.执行方法本身(本前缀增强 和 后缀增强环绕)
Object result = pjp.proceed();
return result;
} catch (Exception e) {
log.warn("方法执行异常,e:{}", e.getMessage());
throw e;
} finally {
// 5、解锁(aop后缀增强)
if (needRelease && lockResult) {
distributeLock.unlock(lockKey);
}
}
}
private String generateLockKey(ProceedingJoinPoint pjp,String specifyPrefix ,String keyGenRule) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
String methodFullName = pjp.getTarget().getClass().getSimpleName() + method.getName();
//1、lockKey前缀;用户不指定前缀则默认为类名+方法名
String prefix = !Strings.isBlank(specifyPrefix) ? specifyPrefix : methodFullName;
Object[] args = pjp.getArgs();
String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);//key: 方法参数名称,val方法入参参数对象本身(含字段值)。方法参数可能多个,故循环添加
}
Expression expression = elExpressionParser.parseExpression(keyGenRule);//el表达式解析"#内容"
// 2、方法名-参数解析结果
return prefix + "-" + expression.getValue(context).toString();//等效map.get(key),context为map,key是el表达式
}
@Override
public int getOrder() {
//5、配置为最小值 在事务切面之前执行
return Integer.MIN_VALUE;
}
}
3、注解的使用1: key直接使用方法入参某个字段
@RefundConcurrentControl(
intervalTimeSeconds = 3,
keyGenRule = "#operator",//类似于你要加锁的key(misId、orderNo、taskCode等)
needRelease = true,
specifyPrefix = "WriteShippingTaskDetail",//描述你加锁操作的目的,具体是对什么操作(创建退货单、导出加量日志等)
specifyErrorMsg = "该单正在操作中,请稍后重试操作")
public void confirmPDAShippingTaskDetail(ConfirmShippingTaskDetailRequest request,String operator) {
}
注解的使用2: key间接使用方法入参request中,某个字段名称
@RefundConcurrentControl(
specifyPrefix = "changePdaPickingTaskTaker",
intervalTimeSeconds = 10,
needRelease = true,
specifyErrorMsg = "正在更改执行人,请勿重复操作",
keyGenRule = "#request.newOperator") //ChangeOperatorTRequest request中的newOperator字段作为key
@Transactional(rollbackFor = Exception.class)
public void changeOperator(ChangeOperatorTRequest request) {
}
注解的使用3: key间接使用方法入参request中,某2个字段组合
@RefundConcurrentControl(
specifyPrefix = "OperateRDCPickingSkuDetail",
intervalTimeSeconds = 2,
needRelease = true,
specifyErrorMsg = "重复性互斥提交,请稍后重试",
keyGenRule = "#request.pickingTaskNo + '-' + #request.pickingTaskSkuDetailId")//SubmitPickingTaskDetailRequest request中的两个字段拼接成key
public SubmitPickingTaskDetailResponse submitPickingTaskDetail(long poiId, SubmitPickingTaskDetailRequest request) {
}
4、分布式锁实现
public interface DistributeLockService {
/**
* 释放锁
*
* @param idempotentKey:key
* @param redisCategory:squirrel上申请的category
*/
void unlock(String idempotentKey, String redisCategory);
/**
* 加锁
*
* @param idempotentKey:key
* @param expireTime:锁有效时长
* @param redisCategory:squirrel上申请的category
*/
boolean lock(String idempotentKey, int expireTime, String redisCategory);
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class RedisGateway {
private static final Logger LOGGER = LoggerFactory.getLogger(SquirrelGateway.class);
@Resource(name = "redisClient0")
private RedisStoreClient redisClient;
/**
* @param storeKey key
* @param keySegments 要增加的步长
* @param expireInSeconds key的过期时间
* @return
*/
public Long buildSquirrel(StoreKey storeKey, long keySegments, int expireInSeconds) throws GatewayException {
LOGGER.info("redisClient信息:{}", redisClient.getClusterName());
try {
return redisClient.incrBy(storeKey, keySegments, expireInSeconds);
} catch (Exception e) {
throw new GatewayException(GATEWAY_EXCEPTION_CODE, "buildSquirrel exception", e);
}
}
/**
* 设置过期
*
* @param storeKey key
* @param expireInSeconds 过期时间
*/
@Retryable
public void expire(StoreKey storeKey, int expireInSeconds) {
LOGGER.info("expire,storeKey:[{}],expireInSeconds:[{}]", storeKey,expireInSeconds);
try {
redisClient.expire(storeKey, expireInSeconds);
} catch (Exception e) {
throw new GatewayException(GATEWAY_EXCEPTION_CODE, "expire exception", e);
}
}
public boolean setNx(StoreKey storeKey, String value, int expireSeconds) {
LOGGER.info("setNx,storeKey:[{}],value:[{}]", storeKey, value);
try {
return redisClient.setnx(storeKey, value, expireSeconds);
} catch (Exception e) {
throw new GatewayException(GATEWAY_EXCEPTION_CODE, "setNx exception", e);
}
}
/**
* 释放锁
* @param idempotentKey
* @param redisCategory
*/
public boolean delete(String idempotentKey, String redisCategory) {
LOGGER.info("Redis delete, key:{}", idempotentKey);
StoreKey storeKey = new StoreKey(redisCategory, idempotentKey);
try {
if (redisClient.exists(storeKey)) {
return redisClient.delete(storeKey);
}
} catch (Exception e) {
LOGGER.error("redis 异常", e);
}
return false;
}
/**
* 尝试加锁
* @param key
* @param redisCategory:Squirrel中申请的category
* @return
*/
@Retryable(exclude = GatewayException.class)
public boolean setNxNew(String key, String value, int expireTime, String redisCategory) {
LOGGER.info("Redis setNx, key:{}", key);
StoreKey storeKey = new StoreKey(redisCategory, key);
try {
return redisClient.setnx(storeKey, value.getBytes(), expireTime);
} catch (Exception e) {
if (e.getMessage().contains("ReadTimeout = ")) {
// 读取超时需要考虑,可能已经set成功了,重试可能会返回false
throw new GatewayException(GATEWAY_READ_TIMEOUT_EXCEPTION_CODE, e.getMessage(), e);
}
// 重试三次
throw e;
}
}
/**
* @param key : key
* @param redisCategory:Squirrel中申请的category
* @return : set成功
*/
public boolean set(String key, Object value, int expireTime, String redisCategory) {
LOGGER.info("Redis set, key:{}", key);
StoreKey storeKey = new StoreKey(redisCategory, key);
try {
return redisClient.set(storeKey, value, expireTime);
} catch (Exception e) {
LOGGER.error("RedisGateway set occurs exception", e);
throw new GatewayException(GATEWAY_EXCEPTION_CODE, e.getMessage());
}
}
public String get(String key, String redisCategory) {
LOGGER.info("Redis get, key:{}", key);
StoreKey storeKey = new StoreKey(redisCategory, key);
try {
return redisClient.get(storeKey);
} catch (Exception e) {
LOGGER.error("RedisGateway get occurs exception", e);
throw new GatewayException(GATEWAY_EXCEPTION_CODE, e.getMessage());
}
}
@Retryable(exclude = GatewayException.class)
public boolean retrySetNx(StoreKey storeKey, String value, int expireSeconds) {
LOGGER.info("retrySetNx,storeKey:[{}],value:[{}],expireSeconds:[{}]", storeKey, value, expireSeconds);
try {
return redisClient.setnx(storeKey, value, expireSeconds);
} catch (StoreException e) {
if (e.getMessage().contains("ReadTimeout = ")) {
// 读取超时需要考虑,可能已经set成功了,重试可能会返回false
throw new GatewayException(GATEWAY_READ_TIMEOUT_EXCEPTION_CODE, e.getMessage(), e);
}
// 重试三次
throw e;
}
}
}
1、定义注解:可加载方法和类上
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AuthConfig {
}
2、使用
@Override
@AuthConfig
public QueryRdcSupplierRefundBillListResponseDTO queryRdcSupplierRefundBill(QuerySupplierRefundBillRequestDTO request) throws TException {
}
3、pom
aspectjweaver
<!--spring的aop支持包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
<!--面向切面支持包,Spring要支持AOP必需要导入这个包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
//aop类实现了Ordered接口
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
4、aop实现
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Slf4j
@Aspect
@Component
public class AuthAspect implements Ordered {
@Autowired
private PoiAuthGateway poiAuthGateway;
//1、想要生效的java下的路径(接口实现类下的方法都可以)
@Before("execution(* com.sankuai.grocerywms.logistics.sharedrefund.service..*TServiceImpl.*(..))")
public void before(JoinPoint joinPoint) throws BusinessException {
// 2、根据方法注解解析接口配置信息(方法上加AuthConfig这个注解的)
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
AuthConfig config = method.getAnnotation(AuthConfig.class);
// 3、根据类注解解析接口配置信息
if (config == null) {
config = method.getDeclaringClass().getAnnotation(AuthConfig.class);
}
// 4、没有配置权限注解, 默认为不需要验证
if (config == null) {
return;
}
//5、如果接口、类上添加了此注解,则走下面逻辑(通过公司组件获取misId + 通过公司组件获取poiId)
// 5.1(可以不用关注)校验渠道, 同时获取到操作的用户-获取misId
LoginInfo loginInfo = LoginUtil.getLoginInfo();
if (!LionUtil.poiAuthSwitch()) {
log.info("未开启仓库ID权限校验, 跳过校验");
return;
}
// 没有配置需要检验的环境
if (loginInfo == null || StringUtils.isBlank(loginInfo.getLogin())) {
log.info(String.format("未获得登陆信息,方法名:%s", method.getName()));
return;
}
log.info("operator:{}", GsonUtil.toJsonString(loginInfo));
// 5.2 获取仓id
String poiIdStr = Tracer.getContext("poi.id");
if (StringUtils.isBlank(poiIdStr)) {
log.info(String.format("未获得poi信息,方法名:%s", method.getName()));
return;
}
Long poiId = Long.parseLong(poiIdStr);
// 5.3加了开关,判断这个仓是否需要鉴权
if (!LionUtil.allPoiAuthSwitch() && !LionUtil.isPoiInAuthList(poiId)) {
log.info(String.format("poi不在灰度列表,方法名:%s", method.getName()));
return;
}
// 6、校验仓库是否合法(根据具体业务判断,这里也可以获取用户手机号,然后查询db是否有数据,有则鉴权通过)具体业务定制具体校验
checkPoiInfo(loginInfo, poiId);
}
/**
* 校验当前操作的仓库ID是否有权限
*/
private void checkPoiInfo(LoginInfo loginInfo, Long poiId) throws BusinessException {
// 1、仓库权限查询(公司内部提供的根据misId + poiId + 仓类型,判定这个人是否有这个仓的权限)
boolean hasPermission = poiAuthGateway.havePoiPermission(loginInfo.getLogin(), poiId);
// 2、无权限抛出异常
if (!hasPermission) {
throw new BusinessException(Constants.OPERATE_FORBIDDEN, "当前用户无仓库[" + poiId + "]操作权限");
}
}
@Override
public int getOrder() {
return Integer.MAX_VALUE;//优先级最高
}
}
1、正常情况下,我们写代码是这样的
@Override
public QueryStcBlackListTResp queryStcBlackList(QueryStcBlackListTReq request) {
QueryStcBlackListTResp response = new QueryStcBlackListTResp();
response.setCode(RespCodeEnum.SUCCESS_CODE.code);
try {
//业务
} catch (IllegalArgumentException e) {
log.warn("queryStcBlackList...参数错误 param: [{}]", GsonUtils.toJsonString(request), e);
response.setCode(RespCodeEnum.PARAM_ERROR.code);
response.setErrMsg(e.getMessage());
} catch (BusinessException e) {
log.error("queryStcBlackList...业务异常 param: [{}]", GsonUtils.toJsonString(request), e);
response.setCode(RespCodeEnum.BIZ_ERROR.code);
response.setErrMsg(e.getMessage());
} catch (Exception e) {
log.error("queryStcBlackList...内部错误 param: [{}]", GsonUtils.toJsonString(request), e);
response.setCode(RespCodeEnum.SERVICE_ERROR.code);
response.setErrMsg(RespCodeEnum.SERVICE_ERROR.desc);
}
return response;
}
每个接口实现类,都要写catch逻辑,根据不同的Exception然后打印不同的日志,所有impl中方法代码都是重复的,都这样
@Override
public QueryStcBlackListTResp queryStcBlackList(QueryStcBlackListTReq request) {
//业务
}
//或这样
@Override
public CreateRefundTResponse createRefundBill(CreateRefundTRequest request) throws TException {
if (request == null) {
throw new IllegalArgumentException("入参不能为空");
}
log.info("创单入参数:{}", GsonUtil.toJsonString(request));
Preconditions.checkArgument(Objects.nonNull(request.getPoiId())&&request.getPoiId() > 0, "仓id不能为空");
Preconditions.checkArgument(Objects.nonNull(request.getRefundBillNo()), "单号不能为空");
// 赋值
if (request.getBillTypeValue().equals(RefundBillTypeEnum.TAKE.code)) {
request.setBillType(RefundBillTypeEnum.TAKE.desc);
request.setTarget(RefundTargetEnum.TAKE.getValue());
} else if (request.getBillTypeValue().equals(RefundBillTypeEnum.DESTROY.code)) {
request.setBillType(RefundBillTypeEnum.DESTROY.desc);
request.setTarget(RefundTargetEnum.DESTROY.getValue());
} else {
throw new BusinessException(Constants.INTERNAL_ERROR, "bill type error");
}
//业务
return refundCmdLocalService.createPcRefundBill(createRefundBillBO, createRefundSkuDetailDTOS);
}
2、aop实现
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @description: 全局异常处理
*/
@Aspect
@Component
@Slf4j
public class TServiceAspect implements Ordered {
//生效的范围是TServiceImpl下所有方法
@Around("execution(* com.xx.xx.xx.xx.service..*TServiceImpl.*(..))")
public Object doAop(ProceedingJoinPoint pjp) throws Throwable {
String targetClassName = pjp.getTarget().getClass().getSimpleName();
MethodSignature signature = (MethodSignature) pjp.getSignature();
String methodName = signature.getName();
String methodFullName = targetClassName + "." + methodName;
Object[] args = pjp.getArgs();
try {
log.info("method called:{},param:{}", methodFullName, GsonUtil.toJsonString(args));
} catch (Exception e) {
log.error("打印请求参数错误", e);
}
Method setCode = Arrays.stream(signature.getReturnType().getMethods())
.filter(method -> "setCode".equalsIgnoreCase(method.getName()))
.findFirst()
.orElse(null);
Method setMsg = Arrays.stream(signature.getReturnType().getMethods())
.filter(method -> "setMsg".equalsIgnoreCase(method.getName()))
.findFirst()
.orElse(null);
if (setMsg == null) {
setMsg = Arrays.stream(signature.getReturnType().getMethods())
.filter(method -> "setMessage".equalsIgnoreCase(method.getName()))
.findFirst()
.orElse(null);
}
LoginInfo loginInfo = LoginUtil.getLoginInfo();
try {
Object result = pjp.proceed();
log.info("method called:{},param:{}, return:{}, loginInfo = {}.", methodFullName, GsonUtil.toJsonString(args), GsonUtil.toJsonString(result), loginInfo);
return result;
} catch (IllegalArgumentException e) {
int code = Constants.BAD_PARAMETERS;
String message = e.getMessage();
log.warn("TServiceExceptionAspect catch IllegalArgumentException, method:{},param:{}, code:{}, message:{}, loginInfo = {}, exception : {} ",
methodFullName, args, code, message, loginInfo, e);
if (setMsg == null || setCode == null) {
throw e;
} else {
Object result = signature.getReturnType().newInstance();
setMsg.invoke(result, e.getMessage());
setCode.invoke(result, code);
return result;
}
} catch (BusinessException e) {
int code = e.getCode();
String message = e.getMessage();
String errorKey = e.getErrorKey();
boolean block = e.isBlock();
log.warn("TServiceExceptionAspect catch BusinessException, method:{},param:{}, code:{}, message:{}, block:{}, errorKey:{}, loginInfo = {}, exception : {}.",
methodFullName, args, code, message, block, errorKey, loginInfo, e);
doCatLog(methodFullName, e);
if (setMsg == null || setCode == null) {
throw e;
} else {
Object result = signature.getReturnType().newInstance();
setMsg.invoke(result, e.getMessage());
setCode.invoke(result, e.getCode());
return result;
}
} catch (Exception e) {
log.error("TServiceExceptionAspect catch Throwable, method:{},param:{}, loginInfo = {}", methodFullName, args, loginInfo, e);
doCatLog(methodFullName, e);
if (setMsg == null || setCode == null) {
throw e;
} else {
Object result = signature.getReturnType().newInstance();
String message = String.format("系统内部错误,错误方法:%s[traceId=%s]", methodFullName, Tracer.id());
setMsg.invoke(result, message);
setCode.invoke(result, Constants.INTERNAL_ERROR);
return result;
}
}
}
private void doCatLog(String methodFullName, Throwable e) {
if (e instanceof BusinessException) {
BusinessException ex = (BusinessException) e;
int code = ex.getCode();
String message = ex.getMessage();
String errorKey = ex.getErrorKey();
boolean block = ex.isBlock();
logBusinessException(methodFullName, ex, code, message, errorKey, block);
} else {
}
}
private void logBusinessException(String methodFullName, Exception ex, int code, String message, String errorKey, boolean block) {
if (code == Constants.GATEWAY_ERROR) {
}
if (block) {
}
if (StringUtils.isNotBlank(errorKey)) {
}
}
@Override
public int getOrder() {
return Integer.MAX_VALUE - 1;
}
}
1、定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Authentication {
/**
* 字段含义: 仓类型
* 是否必填: 是
* 内容 : 2:Rdc 、6: 协同仓,、7:PC加工仓、4: 网店
*/
int poiType();
/**
* 字段含义 : 仓id 或 网店id
* 是否必填 : 是
* 内容 : poiType值为2、6、7时,id值为仓id ;poiType值为4时,id值为网店id
* example: "#request.poiId"、"#request.netPoiId"
* 补充:#,为了el表达式解析
* request,方法入参名称
* poiId或netPoiId是request中字段名称
*/
String id();
}
2、使用注解
@Override
@Authentication(
poiType = 7,
id = "#req.poiId"
)
public StcEditProcessConfigTResp editProcessConfig(StcEditProcessConfigTReq req) {
// 业务
}
3、req
@TypeDoc(description = "编辑-加量链路开关请求")
@ThriftStruct
public class StcEditProcessConfigTReq {
@FieldDoc(description = "仓id", requiredness = Requiredness.REQUIRED)
private Long poiId;
@ThriftField(1)
public Long getPoiId() {
return this.poiId;
}
@ThriftField
public void setPoiId(Long poiId) {
this.poiId = poiId;
}
}
4、aop类
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
@Slf4j
@Aspect
@Component
public class AuthenticationAspect implements Ordered {
private SpelExpressionParser elExpressionParser = new SpelExpressionParser();
@Resource
private DataAuthService dataAuthService;
@Around(
"@annotation(com.x.x.x.x.biz.service.aop.Authentication)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 1、反射获取方法信息(方法、注解等)
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Authentication annotation = method.getAnnotation(Authentication.class);
// 2.获取注解信息(仓类型 和 仓|网店id)
int poiType = annotation.poiType();
String poiIdOrNetPoiId = expressPoiOrNetPoiId(pjp, annotation.id());
// 3、获取misId
String misId = UserUtil.getMis();
// 4.根据misId 和 仓|网店id 鉴权
ExceptionMessageCollector collector = new ExceptionMessageCollector();
if (dataAuthService.checkPcXtcAuth(misId, Lists.newArrayList(Long.valueOf(poiIdOrNetPoiId)), poiType, collector)) {
return pjp.proceed();
}
throw new BusinessException(ResultCodeEnum.AUTH_ERROR.getCode(), collector.toString());
}
private String expressPoiOrNetPoiId(ProceedingJoinPoint pjp, String id) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 1.获取增强方法的所有请求参数对象
Object[] args = pjp.getArgs();
// 2.获取增强方法的所有请求参数名称
String[] paramNames = signature.getParameterNames();
// 3.map即context添加 key(请求参数名称) - val(请求参数对象)
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
// 4.参数解析结果,类似map.get(key),只不过map为context, key为转换为el表达式的expression
Expression expression = elExpressionParser.parseExpression(id);
Object poiOrNetPoiId = expression.getValue(context);
if (poiOrNetPoiId != null) {
return poiOrNetPoiId.toString();
}
return StringUtils.EMPTY;
}
@Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
5、test
@Resource
private StcSellOutIncreaseProcessTService stcSellOutIncreaseProcessTService;
@Test
public void testAop() {
StcEditProcessConfigTReq req = new StcEditProcessConfigTReq();
req.setPoiId(143180124832199L);
req.setSellOutOrTime("19:45");
req.setSwitchStatus(1);
// 用户有仓的权限
Tracer.putContext("mall.sso.user", "{\"login\": \"zhangsan\"}");
StcEditProcessConfigTResp resp = stcSellOutIncreaseProcessTService.editProcessConfig(req);
// 用户没有仓的权限
racer.putContext("mall.sso.user", "{\"login\": \"zhangsanaaa\"}");
StcEditProcessConfigTResp resp = stcSellOutIncreaseProcessTService.editProcessConfig(req);
}