spring利用aop灵活mock测试数据

涉及技术点: aop+js引擎+redis+动态参数替换

在需要模拟的接口或controller加注解即可

1.编写注解类,其中cls在返回类型为list或数组时候,子对象的类,filter为过滤器条件,如果需要放行某些条件下的数据可以开启,需要redis上加key=类名.方法名.filter,value为一段js脚本字符串

import java.lang.annotation.*;

/**
 * @description: 拦截注解
 * @author: moodincode
 * @create: 2020/10/26
 **/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TestMethod {
    //数组对象默认返回值的T对象,数组类型无法获取默认泛型
    Class cls() default Object.class  ;
    //是否使用条件比较,如果使用条件比较,则判断条件是否满足,从redis从是否,类名.方法名.filter读一段js脚本执行,返回true-则模拟数据,false走原来接口
    boolean filter() default false;
}

2.编写正则表达式替换带参内容类

/**
 * @description:
 * @author: moodincode
 * @create 2020/12/24
 */
public class TestRegMatchUtil {
    public static final String REGEX = "#\\{[^}]*\\}";

    /**
     * 获取匹配字段
     * @param content
     * @return
     */
    public static Map<String,String> getAtMapKeyValue(String content){
        Map<String,String> map=new HashMap<>();
        Pattern pattern = Pattern.compile(REGEX);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            for(int i=0; i<=matcher.groupCount(); i++){
                String key = matcher.group(i);
                String value=key.substring(0,key.length()-1).replaceFirst("#\\{","");
                map.put(key,value);
            }
        }
        return map;
    }
}

3.编写aop拦截器,拦截器中涉及json工具类和redis获取对象的数据自己实现,其中redis可以根据条件更改为读数据库数据或者配合配置中心自动刷新读取配置文件


/**
 * @description: 压测拦截接口返回指定结果的测试AOP
 * @author: moodincode
 * @create: 2020/10/26
 **/
@Aspect
@Order(1)
@Configuration
public class TestOperatorHandlerAspect {

    private static Logger log = LoggerFactory.getLogger(TestOperatorHandlerAspect.class);
    /**
     * 是否开启debug true 开启,false-关闭
     */
    @Value("${test.openDebug:false}")
    private Boolean openDebug;
    @Resource
    private RedisService redisService;
    /**过滤前缀**/
    private static final String FILTER_PREFIX=".filter";
    /**过滤前缀**/
    private static final String BOOLEAN_STR="true";

    /**
     * 拦截api接口
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("@annotation(TestMethod)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //判断是否开启debug模式
        if (!openDebug) {
            return point.proceed();
        }
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        try {
            // 方法名
            String methodName = methodSignature.getName();
            // 类名
            String className = methodSignature.getDeclaringTypeName();
            //构建redis中的key=类名.方法名
            String key = className.substring(className.lastIndexOf(".") + 1) + "." + methodName;
            TestMethod testMethod = methodSignature.getMethod().getAnnotation(TestMethod.class);
            //如果开启过滤条件模式,则判断条件是否满足
            if(testMethod.filter()){
                //条件不满足,则执行原方法
                if(!calculateCondition(point, key)){
                    return point.proceed();
                }

            }

            log.info("测试接口api拦截,类名={},方法名为:{},测试key={}", className, methodName, key);
            Object data = redisService.get(key, Object.class);
            log.info("测试接口api拦截:redis值为{}", data);
            String jsonString;
            if (data == null) {
                return point.proceed();
            } else if (data instanceof String) {
                jsonString = (String) data;
            } else {
                jsonString = JsonUtil.toJsonString(data);
            }

            if (!StringUtils.isEmpty(jsonString)) {
                Class returnType = methodSignature.getReturnType();
                //获取关键字 json字符串中包含@{}的内容
                Map<String, String> mapKeyValue = TestRegMatchUtil.getAtMapKeyValue(jsonString);
                if(!CollectionUtils.isEmpty(mapKeyValue)){
                    //js引擎处理
                    ScriptEngine javaScript=new ScriptEngineManager().getEngineByName("JavaScript");
                    //填充入参,@{}内容的js脚本可以通过数组获取参数
                    javaScript.put("args",point.getArgs());
                    //遍历替换的值,替换内容
                    for (Map.Entry<String, String> entry : mapKeyValue.entrySet()) {
                        if(!StringUtils.isEmpty(entry.getValue())){
                            try {
                                Object eval = javaScript.eval(entry.getValue());
                                if(eval!=null){
                                    jsonString= jsonString.replace(entry.getKey(),eval.toString());
                                    //替换相同参数的
                                    while (jsonString.contains(entry.getKey())){
                                        jsonString= jsonString.replace(entry.getKey(),eval.toString());
                                    }
                                }
                            }catch (Exception e){
                                log.error("表达式值计算报错,原因:",e);
                            }

                        }
                    }


                }
                //解析数据
                return parseData(jsonString, returnType,testMethod);
            }

        } catch (Exception e) {
            log.error("测试接口拦截出错,原因:", e);
        }
        return point.proceed();
    }

    /**
     * 判断过滤条件
     * @param point
     * @param key
     * @return
     * @throws ScriptException
     */
    private boolean calculateCondition(ProceedingJoinPoint point, String key) throws ScriptException {
        String filterKey = key + FILTER_PREFIX;
        String jsStr = redisService.get(filterKey, String.class);
        //未设置,则不拦截
        if(!StringUtils.isEmpty(jsStr)){
            //js引擎处理
            ScriptEngine javaScript=new ScriptEngineManager().getEngineByName("JavaScript");
            javaScript.put("args", point.getArgs());
            try {
                Object eval = javaScript.eval(jsStr);
                if(eval==null){
                    return false;
                }else if(eval instanceof String && BOOLEAN_STR.equalsIgnoreCase((String) eval)){
                    return true;
                }else if(eval instanceof Boolean &&Boolean.TRUE.equals(eval)){
                    return true;
                }else{
                    return false;
                }
            } catch (ScriptException e) {
                log.error("mock条件执行失败,原因",e);
               return false;
            }

        }
        return true;
    }

    /**
     * 解析数据
     * @param testMethod
     * @param jsonString
     * @param returnType
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public Object parseData(String jsonString, Class returnType,TestMethod testMethod) throws InstantiationException, IllegalAccessException {
        if (returnType == Void.class) {
            log.info("无参数,返回");
            return null;
        } else if (returnType ==Boolean.class){
            return Boolean.valueOf(jsonString);
        }else if (returnType ==Long.class){
            return Long.valueOf(jsonString);
        }
        Object object;
        if (returnType.isArray() || List.class.isAssignableFrom(returnType) || returnType.newInstance() instanceof List) {
            object = JSON.parseArray(jsonString, testMethod.cls());
        } else {
            object = JSON.parseObject(jsonString, returnType);
        }


        log.info("测试返回结果:{}", JSON.toJSONString(object));
        return object;
    }
}

4. 在需要模拟的方法上加@TestMethod的注解,如果返回的对象是数组或arrayList,请补充@TestMethod(cls=元对象.class)

 

5.配置文件上test.openDebug=true,开启注解

6.在redis上新建key=类名.方法名,value=json对象或json字符串,如果返回的是基本类型的,请使用字符串,例如BOOLEAN,value="true",

可使用#{}来注入js计算判断,例如{"timestamp":"#{''+Math.round(new Date().getTime()/1000)}"}

value中可使用args数组对象,为方法的入参,例如 add(int a,int b), #{args[0]}为a,#{args[0]}为b

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心情加密语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值