实现接口发送记录和补偿操作
背景
一个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();
}
}
}
}
以上就把这个数据重新执行了
是不是很方便
可以根据自己的情况进行调整和升级
最后希望能帮助到大家,不足之处,希望大家指教