java spring 实现接口发送记录和补偿操作

实现接口发送记录和补偿操作

背景

一个web项目免不了要和别的系统进行系统交互,不管是手动触发,还是定时发送,都需要保存记录,如果发送失败可能还需要查看失败原因或者进行补偿发送

但是不同的接口可能出入参不一样,发送方式也不一样,调用的方法方式也不一样,可能还是老代码,aop使用也不一定合适,主要老旧代码可能不同的人写,无法用aop得到返回参数

下面介绍我在项目中使用的一种方式,希望能给大家提供帮助和思路,不足之处希望大家进行指教

创建实体

public class MethodRecord implements Serializable {

    private String id;
    private String name;//接口名称
    private String url;//接口地址,只用来记录展示
    private Integer status;//0失败 1成功
    private String retInfo;//返回参数
    private Date createTime;//创建时间
    private String param;//入参
    private byte[] data;//入参byte
    private String className;//所在类
    private String methoName;//执行方法
    
  }

从上面的实体中可以猜到基本的实现逻辑

通过反射执行方法,需要做的就是把当前参数保存起来,data 是参数序列化后用来保存和执行的,param是用来展示的,这样分开主要是为了方便,序列化和反序列化进行的更加方便

然后添加以下get set 构造函数 就好了

最重要的是要实现Serializable 参数实体也要实现 Serializable 不然不能实例化

创建执行方法


public class MethodMain<T> {

    private Logger log = LoggerFactory.getLogger(MethodMain.class);

    /**
     * 工厂方法创建
     */
    public static <T> MethodMain factoryCreate(T t){
        return new MethodMain(t);
    }

    private MethodRecord record;
    private Map<String,Object> obj;

    public MethodMain(){
        record = new MethodRecord(new Date());
        record.setId(CTools.getUUID());
        obj = new LinkedHashMap<>();
    }

    public MethodMain(T t){
        this();
        record.setClassName(t.getClass().getName());
    }


    public void run(){

        getDataStr();
        getData();

        new MethodRunnable(this.record);
    }
    //方法和类
    public MethodMain addClassName(Class<?> cc){
        record.setClassName(cc.getName());
        return this;
    }

    public MethodMain addMethodName(String name){
        record.setMethoName(name);
        return this;
    }


    //接口名
    public MethodMain addName(String name){
        record.setName(name);
        return this;
    }
    //方法地址
    public MethodMain addUrl(String url){
        record.setUrl(url);
        return this;
    }
    //返回结果
    public MethodMain addResult(String result){
        record.setRetInfo(result);
        return this;
    }

    public MethodMain addResult(JSONObject result){
        record.setRetInfo(result.toJSONString());
        return this;
    }
    public MethodMain addResult(Integer result){
        record.setRetInfo(String.valueOf(result));
        return this;
    }
    //0 失败  1成功
    public MethodMain addStatus(MethodStatus s){
        record.setStatus(s.num);
        return this;
    }

    public MethodMain addParam(String name,Object value){
        obj.put(name,value);
        return this;
    }


    public void getDataStr(){

        JSONObject json = (JSONObject) JSONObject.toJSON(obj);
        record.setParam(json.toJSONString());
    }

    private  void getData() {
        //将参数转成二进制流
        ObjectOutputStream oos = null;
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(out);
            oos.writeObject(obj);
            oos.flush();
            byte[] data = out.toByteArray();
           // log.info("数据长度 {}",data.length);
            record.setData(data);

        } catch (Exception e) {
            log.error("流转换失败,请确定类型是否序列化");
            log.error(e.getMessage(), e);
        } finally {

            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

这个类主要是生成 上面的这个MethodRecord 实体,然后将这个实体保存到数据库

可以看到参数使用的是 LinkedHashMap 进行封装,为的是这样保证参数的顺序,再反序列化的时候可以保存参数位置

这里有些异常没有抛出,建议使用的时候注意异常的抛出,否则后期很多问题不好排查

成功失败状态枚举 MethodStatus

public enum MethodStatus {

    OK(1),ERR(0);

    Integer num;

     MethodStatus(Integer num){this.num = num; }
}

数据将数据保存的线程 就是上面的run() 方法中可以看到

大家自己实现保存的逻辑记好了,当然得先创建好MethodRecord 的service dao 层等,这里就不进行展示了描述了

public class MethodRunnable implements Runnable{

    private Logger log = LoggerFactory.getLogger(MethodRunnable.class);
    private MethodRecord record;
    private MethodService service;//这个自己创建就好了
    private ThreadPoolExecutor th;//线程池 也可以不用,根据自己的情况实现就好

    public MethodRunnable(MethodRecord record){
        //log.info("开始保存数据");
        this.record = record;
        this.service = SpringContextHolder.getBean(MethodService.class);//获取spring 容器
        this.th = SpringContextHolder.getBean(ThreadPoolExecutor.class);//获取spring 容器
        th.execute(this);
    }

    @Override
    public void run() {
        //log.info("{}",record);
        //进行保存
        service.save(record);
    }


}

以上一个工具类就好了,下面看下使用

使用

使用可以是AOP的方式也可以放入方法中,这个根据自己场景就可,我这里是直接嵌入到方法中的

    public String getData(String param){

        MethodMain.factoryCreate(this)
                .addMethodName("getData")
                .addName("测试接口1")
                .addUrl("https://baidu.com")
                .addStatus(MethodStatus.OK)
                .addParam("param",param)
                .addResult("result")
                .run();

        return "result";
    }
    
    
    public JSONObject getData(String param,Integer id){

        JSONObject ret = new JSONObject().fluentPut("code",200).fluentPut("data","123456");

        MethodMain.factoryCreate(this)

                .addMethodName("getData")
                .addName("测试接口2")
                .addUrl("https://baidu.com")
                .addStatus(MethodStatus.ERR)
                .addParam("param",param)
                .addParam("id",id)
                .addResult(ret)
                .run();

        return ret;
    }

以上两个测试方法就是了

使用的是链式方法当然也可以分开, .run() 后 就可以将数据进行保存了。

补偿执行

当发现接口失败后获取数据进行补偿操作

首先通过id 获取到这个数据,下面就是执行方法

 public boolean exe(MethodRecord record) {
        ObjectInputStream ois = null;
        ByteArrayInputStream bin = null;
        try {
            //获取到实体
            MethodRecord entity = findById(record);

             bin = new ByteArrayInputStream(entity.getData());

             ois =  new ObjectInputStream(bin);
			//将参数byte转成map
            LinkedHashMap<String,Object>  param = (LinkedHashMap)(ois.readObject());

            String className = entity.getClassName();
            String methodName = entity.getMethoName();

            Class<?> bean = Class.forName(className);

            //下面就是组装参数和类型
            Class<?>[] claArr = new Class[param.size()];
            Object[] args = new Object[param.size()];
            Iterator<Map.Entry<String,Object>> iter = param.entrySet().iterator();
            int index = 0;
            while(iter.hasNext()){
                Map.Entry<String,Object> item = iter.next();
                Object val = item.getValue();
                Class<?> child = val.getClass();
                claArr[index] = child;

                args[index++] = val;
            }

			//获取要执行的方法
            Method method = bean.getMethod(methodName,claArr);
            log.info(bean.getName());
            String beanNameAll = bean.getName();
            String beanName = beanNameAll.substring(beanNameAll.lastIndexOf(".")+1);
            String newName = beanName.substring(0,1).toLowerCase() + beanName.substring(1);
            //执行方法
            Object ret =  method.invoke(SpringContextHolder.getBean(newName),args);

            //log.info("{}-{}  执行结果 {}",className,methodName,ret);

            //到这里数据就执行成功了 删除执行的这条记录
            delete(record);

            return true;


        } catch (ClassNotFoundException e) {
            log.error(e.getMessage(),e);
            return false;

        } catch (NoSuchMethodException e) {
            log.error(e.getMessage(),e);
            return false;
        } catch (InvocationTargetException e) {
            log.error(e.getMessage(),e);
            return false;
        } catch (IllegalAccessException e) {
            log.error(e.getMessage(),e);
            return false;
        } catch (IOException e) {
            log.error(e.getMessage(),e);
            return false;
        }finally {
            if(bin != null){}
            try {
                bin.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            if(ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

以上就把这个数据重新执行了

是不是很方便

可以根据自己的情况进行调整和升级

最后希望能帮助到大家,不足之处,希望大家指教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值