测试老兵 生产环境过滤测试账号--已完结

CC · 2018年12月06日 · 最后由 CC 回复于 2018年12月10日 · 4566 次阅读

引言:

从天美的王者荣耀引发的测试数据展示到生产环境问题,及目前很多实际情况,需要测试在生产环境进行验证,如何把测试账号的数据从生产环境上过滤掉,但测试又可以在线上进行验证,这确实是个比较头疼的事情,我司也存在类似问题,我这边在进行预演,先说下预演情况,实际还是未真正解决(主要是希望优雅的解决)

方案一--AOP 切面

策略

采用 AOP 切面方式,把对应的 DAO 层文件做切面,把返回结果中过滤掉测试账号 ID

优势

可以指定特定 DAO 文件及特定方法进行处理

劣势

编写规则需要规范

预演代码

package com.finger.test.common.net;

import com.finger.test.pojo.UserAccountDO;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * @Des: mybatis结果集拦截器
 * @Auther: 飞狐
 * @Date: 2018/12/6
 */

public class SelectResultInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable{

        String className = methodInvocation.getClass().getName();
        String methodName = methodInvocation.getMethod().getName();

        Object result = methodInvocation.proceed();
        if(className.equals("UserAccountDOMapper")){
            if(methodName.startsWith("select")){

                UserAccountDO userAccountDO = (UserAccountDO) result;

                System.out.println(userAccountDO.getAccountNo());
            }
        }

        return result;
    }
}

拦截器 XML 配置

<bean id = "selectResultInterceptor" class="com.finger.test.common.net.SelectResultInterceptor" />
  <aop:config>
      <aop:pointcut id = "selectResultInterceptorCut" expression="execution(* com.finger.test.dao.*Mapper.*(..))" />
      <aop:advisor pointcut-ref="selectResultInterceptorCut" advice-ref="selectResultInterceptor"/>
  </aop:config>

方案二--mybatis 拦截器方案

策略

采用 mybatis 拦截器方案,针对 mybatis 底层 query 方法的返回结果进行拦截,过滤测试账号

优势

不区分 DAO 的命名规范,可随意定义

预演代码

package com.finger.test.common.net;

import com.finger.test.pojo.UserAccountDO;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.ArrayList;
import java.util.Properties;

/**
 * @Des:
 * @Auther: 飞狐
 * @Date: 2018/12/6
 */
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class }) })
public class InterceptorQuery implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable{

        MappedStatement statement = (MappedStatement)invocation.getArgs()[0];
//        statement.getStatementType().
//        System.out.println(statement.getSqlSource());


        Object result = invocation.proceed();

        if(result instanceof ArrayList){
            ArrayList resultList = (ArrayList) result;

            for(int i = 0; i < resultList.size(); i++){
                if(resultList.get(i) instanceof UserAccountDO){
                    UserAccountDO userAccountDO = (UserAccountDO) resultList.get(i);
                    if(userAccountDO.getUserId().equals(16904380000L)){
                        result = null;
                    }
                }
            }

        }

        return result;
    }

    public Object plugin(Object target){
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties){

    }
}

mybatis-config XML 配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 全局配置参数需要时再设置 -->
    <settings>
        <!--&lt;!&ndash; 打开延迟加载 的开关 &ndash;&gt;-->
        <!--<setting name="lazyLoadingEnabled" value="true"/>-->
        <!-- 将积极加载改为消极加载即按需要加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 开启二级缓存  默认也是开启的-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <plugins>
        <plugin interceptor="com.finger.test.common.net.InterceptorQuery"></plugin>
    </plugins>

</configuration>

方案三 -- 自定义注解形式处理

说明

在对应的 DAO 层添加注解,在执行 sql 语句前,在注解层添加过滤测试账号的数据到 sql,生成新的 sql 语句进行执行

目前暂未预演此代码

方案四 -- 最不优雅的方式

说明

把测试账号统一写入到 redis,业务层把原账号 ID 输入到该方法中,返回过滤结果的 ID 列表

弊端

不用说,业务调用的地方太多,代码看起来非常冗余

综合考虑以上四种方式

还是无法解决,部分接口,可能是不想过滤测试账号,部分接口又是要过滤测试账号,又想代码优雅的情况

  1. 前面 2 种是一刀切方案,但好处是无感知;
  2. 第三种方案可能会让开发重新写一个一摸一样的 DAO 层方法,从而区别是否过滤测试账号,在已有的业务上增加,也是较大的工作量
  3. 第四种方案就不多说了,谁都看到是最笨的方案了

也想知道下其他同学有没好的方案建议

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 10 条回复 时间 点赞

拦截 + 黑白名单,黑白名单就是一个列表,放哪里都可以,主要是你开心😃

CC #9 · 2018年12月07日 Author
bauul 回复

陈大大,来点细化思路?如何保证我这边在客户端部分接口要封杀?后台部分不做封杀?

CC 回复

周末一起吃饭,慢慢探讨

CC #4 · 2018年12月07日 Author
bauul 回复

没时间,最近连续一个多月 10 点多回去,圣诞节前太忙了

造测试账号的时候 就打测试标,然后在某一层,根据打标判断下呗。

CC #6 · 2018年12月07日 Author
恒温 回复

阿里童鞋做法,嗯,我去看下

app 可以用灰度包,发版的时候灰度包过了,再发生产。
生产环境过滤测试账号,不符合环境分离原则,公司稽查会报这个风险。

CC #8 · 2018年12月10日 Author
KK 回复

可能每个公司的情况不太一样,我们这边有时候一些测试是需要在生产环境做一定的验证,但这些账号希望对用户而言是 屏蔽的,对我们自己后台可以,是可见,可过滤的,等同测试账号要在部分接口完全屏蔽,部分接口非完全屏蔽

我们目前是用的灰/黑名单策略,进行线上功能测试验证,后续有一个小的 job,定时根据打的测试标签,清理线上测试数据。

CC #10 · 2018年12月10日 Author
Joo 回复

你讲的是清数据一块,也是很赞的思路,那你们的黑灰名单除了数据清理作用以外,在部分接口是对用户不可见的吗?实现测试是业务层要手动判断一层黑白名单吗

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册