Springboot项目添加接口入参统一打印

需求:要求接口被调用时要打印被调用方法名,以及入参情况,参数格式化时选择fastjson

注:使用fastjson序列化时脱敏,建议入参统一使用自定义的对象类型作为入参
如果不需要参数脱敏,直接使用增强中相关代码,并去除参数脱敏相关代码即可

  1. 新建注解,用于实现参数打印功能的增强
    该增强可以加在类上,方法上
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ParamInfo {

    /**
     * 取消统一打印参数
     * 默认为false统一打印
     * 如需自定义参数打印 请赋值为true
     */
    boolean unPrint() default false;

    /**
     * 需要脱敏的字段,如密码等
     */
    String[] ignoreFields() default {};


}
  1. 自定义序列化规则
    基于fastjson实现,可以根据自己的业务具体实现
/**
 * 序列化过滤器:值替换
 *
 */
public class ReplaceFieldFilter implements ValueFilter {

    /**
     * 需要进行替换的属性名和替换值
     * key:属性名
     * value:替换值
     */
    private Map<String, Object> fieldMap;

    public ReplaceFieldFilter() {
    }

    public ReplaceFieldFilter(Map<String, Object> fieldMap) {
        this.fieldMap = fieldMap;
    }
    
    /**
     * @param o     被格式化的对象本身
     * @param name  本次处理的属性名
     * @param value 本次处理的属性值
     * @return
     */
    @Override
    public Object process(Object o, String name, Object value) {
        if(!CollectionUtils.isEmpty(fieldMap)){
            Iterator<Map.Entry<String, Object>> iterator = fieldMap.entrySet().iterator();
            while (iterator.hasNext()){
                Map.Entry<String, Object> next = iterator.next();
                if(next.getKey().equalsIgnoreCase(name)){
                    return next.getValue();
                }
            }

        }
        return value;
    }

    public Map<String, Object> getFieldMap() {
        return fieldMap;
    }

    public void setFieldMap(Map<String, Object> fieldMap) {
        this.fieldMap = fieldMap;
    }

    /**
     * 传入需要脱敏的字段名,序列化时格式化为 * 号
     */
    public ReplaceFieldFilter(String... fields) {
        String str = "******";
        fieldMap = new HashMap<>(4);
        for (String field : fields) {
            fieldMap.put(field, str);
        }
    }
}
  1. 写参数打印增强,这里选择环绕增强
    方便打印出入参,还有可以自定义异常的返回值
@Component
@Aspect
//表示增强的执行顺序,如果多个增强,数值小的先被执行
@Order(0)
public class ParamInfoAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(ParamInfoAspect.class);
	private static final ReplaceFieldFilter DEFAULT_FIELD_FILTER = new ReplaceFieldFilter("password");

	/**
	 * 方法自定义脱敏规则
	 * key:方法名,value:具体的规则
	 */
	private static final Map<String, ReplaceFieldFilter> OTHER_FIELD_FILTER = new HashMap<>();

	@Value("${response.timeout:10000}")
	private long timeoutMs;

	@Around("@within(com.annotations.ParamInfo) || @annotation(com.annotations.ParamInfo)")
	public Object printParam(ProceedingJoinPoint joinPoint) throws Throwable {

		long startTime = System.currentTimeMillis();

		Object result = null;
		String methodName = null;
		boolean notAlarm = false;

		try {
			Object[] args = joinPoint.getArgs();

			// 获取方法对象与注解
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			Method method = signature.getMethod();
			methodName = method.getName();
			ParamInfo annotation = JoinPointUtils.getAnnotation(method, ParamInfo.class, false);

			// 超时无需告警
			notAlarm = annotation.notAlarm();

			//如果无需打印参数,直接跳过,IP地址可根据自己实际场景定义实际的获取规则
			if (annotation.unPrint()) {
				result = joinPoint.proceed();
				LOGGER.info("clientIp:[{}], method:[{}]", RpcContext.getContext().getRemoteHost(), methodName);
				return result;
			}

			// 如果存在脱敏字段,则获取自定义规则,如果未获取到则新增规则
			ReplaceFieldFilter filter = DEFAULT_FIELD_FILTER;
			String[] fields = annotation.ignoreFields();
			if (fields.length > 0) {
				filter = OTHER_FIELD_FILTER.computeIfAbsent(methodName, (k) -> new ReplaceFieldFilter(fields));
			}

			//参数整合,多字段入参整合为对象,单个对象入参格式不变
			Object param;
			if (args.length == 1) {
				param = args[0];
			}
			else if (args.length == 0) {
				param = new LinkedHashMap<>();
			}
			else {
				Map<String, Object> paramMap = new LinkedHashMap<>();
				String[] parameterNames = signature.getParameterNames();
				for (int i = 0; i < parameterNames.length; i++) {
					paramMap.put(parameterNames[i], args[i]);
				}
				param = paramMap;
			}

			LOGGER.info("clientIp:[{}], method:[{}], parameter:[{}]", RpcContext.getContext().getRemoteHost(), methodName, JSON.toJSONString(param, filter));
			result = joinPoint.proceed();
			return result;

		} catch (Throwable t) {
			LOGGER.error("system is error:", t);

		   //可在这里定义程序异常时的错误返回值
			result = JoinPointUtils.getRightReturn(joinPoint, ErrorCode.SYSTEM_ERROR);
			return result;

		} finally {

			long cost = System.currentTimeMillis() - startTime;
			LOGGER.info("result:[{}]", result);
			LOGGER.info("request cost:{}ms", cost);

			// 超时触发告警
			if (!notAlarm && cost > timeoutMs) {
				LOGGER.error("interface response time exceeds {}ms, please check it! methodName:[{}]", timeoutMs, methodName);
			}
		}
	}
}

4.切点工具类
用于增强中的一些常用操作的封装

/**
 * AOP切点工具类
 *
 * @author zhangYiCong
 * @date 2022-04-15 13:33:23
 */
public class JoinPointUtils {

    /**
     * 从切点获取方法对象
     *
     * @param joinPoint
     * @return
     */
    public static Method getMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod();
    }

    /**
     * 从切点获取指定注解
     *
     * @param joinPoint
     * @param annotationClass
     * @param <T>
     * @return
     */
    public static <T extends Annotation> T getAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
        return getAnnotation(joinPoint, annotationClass, true);
    }

    /**
     * 从切点获取指定注解
     *
     * @param joinPoint
     * @param annotationClass
     * @param alwaysOnMethod  如果为false,方法上获取不到时,会从类上获取
     * @param <T>
     * @return
     */
    public static <T extends Annotation> T getAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass, boolean alwaysOnMethod) {
        Method method = getMethod(joinPoint);
        return getAnnotation(method, annotationClass, alwaysOnMethod);
    }

    /**
     * 从方法上获取指定注解
     *
     * @param method
     * @param annotationClass
     * @param alwaysOnMethod  如果为false,方法上获取不到时,会从类上获取
     * @param <T>
     * @return
     */
    public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass, boolean alwaysOnMethod) {

        //获取方法是否被注解标记
        T annotation = method.getAnnotation(annotationClass);

        //如果在方法上已经获取到注解或者指定只从方法找,直接返回
        return annotation != null || alwaysOnMethod ? annotation : method.getDeclaringClass().getAnnotation(annotationClass);
    }

    /**
     * 传入错误码和响应,封装后返回实际的响应对象
     *
     * @param joinPoint
     * @param code
     * @param message
     * @return
     */
    public static Object getRightReturn(ProceedingJoinPoint joinPoint, int code, String message) {

        // 获取方法的签名对象
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Class<?> returnType = method.getReturnType();

        JSONObject returnResult = new JSONObject();
        returnResult.put("code", code);
        returnResult.put("message", message);

        return returnResult.toJavaObject(returnType);
    }
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值