前言

第三方支付接过不?支付回调的代码写过不?

1.接受支付平台的回调信息,验签判断是否是合法回调 
2.调用支付平台查询接口查询订单
3.获取支付状态,成功还是失败
4.支付状态为成功,处理业务
5.返回服务器报文

哪些步骤可能会出错?

第一步可能出错,验签失败
第二步可能查询不到订单,订单是伪造的
第三步支付状态为失败
第四步业务重复处理,报异常

因为本公司的项目都是输出在一个日志文件上,排查问题,就很不方便。

a业务的日志1
b业务的日志1
c业务的日志1
a业务的日志2

单个业务的日志不在一起显示

有一天,有个线上项目是一个商城类的,客户充值到钱包里面,支付了5笔,但实际只成功了3笔。一开始我以为是第三方支付的锅,直接@那边的技术人员,说你们是不是没有回调我们接口。他们查看日志说已经回调了,我看了下我这边的日志,确实是回调了。

那问题在哪呢?由于一开始我这边只打印了回调的信息,并没有针对每个步骤进行打印。改进之后

···代码
log.info(回调信息)
···代码
log.info(验签结果)
···代码
log.info(订单查询结果)
···代码
log.info(支付状态)
···代码
log.info(处理业务的结果)

最后发现打印日志,是东一块,西一块,很乱,排查问题还是很不方便,而且如果回调都正常,还是会打印日志,就有点浪费。

大家应该都用过Mybatis,有没有觉得Mybatis报错了,问题很快就可以排查到

android 支付宝支付完成回调app 支付回调失败怎么处理_mybatis

resource:存储异常存在于哪个资源文件中。

如:### The error may exist in mapper/TagMapper.xml

activity:存储异常是做什么操作时发生的。

如:### The error occurred while setting parameters

object:存储哪个对象操作时发生异常。

如:### The error may involve defaultParameterMap

message:存储异常的概览信息。

如:### Cause: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'name' at row 1

sql:存储发生日常的 SQL 语句。

如:### SQL: insert into tag (`name`)     values (?)

cause:存储详细的 Java 异常日志。

如:### Cause: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'name' at row 1
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:62)
	at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:144)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)
	at com.sun.proxy.$Proxy4.save(Unknown Source)
	at com.test.error.ErrorContextTest.main(ErrorContextTest.java:31)

于是我照葫芦画瓢,自定义了一个 ErrorPayContext

/**
     * 请求参数
     */
    private String request;

    /**
     * 订单号
     */
    private String orderCode;

    /**
     * 验签结果
     */
    private String check;

    /**
     *  订单查询返回结果
     */
    private String queryOrderResult;

    /**
     * 支付状态
     */
    private String payStatus;

    /**
     * 业务处理返回结果
     */
    private String businessResult;

    /**
     * message
     */
    private String message;

    /**
     * 异常详细信息
     */
    private Throwable cause;

单例模式

// 私有化构造
private ErrorPayContext() {}

// 公共的创建对象方法
public static ErrorPayContext instance() {
    ErrorPayContext errorPayContext = LOCAL.get();
    if (errorPayContext == null) {
        errorPayContext = new ErrorPayContext();
        LOCAL.set(errorPayContext);
    }
    return errorPayContext;
}

这里用到了ThreadLocal本地线程存储,它的作用是为变量在每个线程中创建一个副本,每个线程内部都可以使用该副本,线程之间互不影响。

private static final ThreadLocal<ErrorPayContext> LOCAL = new ThreadLocal<>();

使用 ThreadLocal 来管理 ErrorContext:

保证了在多线程环境中,每个线程内部可以共用一份 ErrorContext,但多个线程持有的 ErrorContext 互不影响,保证了异常日志的正确输出。

set 方法

public ErrorPayContext request(String request) {
    this.request = request;
    return this;
}

public ErrorPayContext orderCode(String orderCode) {
    this.orderCode = orderCode;
    return this;
}

public ErrorPayContext check(String check) {
    this.check = check;
    return this;
}

public ErrorPayContext queryOrderResult(String queryOrderResult) {
    this.queryOrderResult = queryOrderResult;
    return this;
}

public ErrorPayContext payStatus(String payStatus) {
    this.payStatus = payStatus;
    return this;
}

public ErrorPayContext businessResult(String businessResult) {
    this.payStatus = payStatus;
    return this;
}

public ErrorPayContext message(String message) {
    this.message = message;
    return this;
}

public ErrorPayContext cause(Throwable cause) {
    this.cause = cause;
    return this;
}

释放资源

public ErrorPayContext reset() {
    request = null;
    check = null;
    orderCode = null;
    queryOrderResult = null;
    payStatus = null;
    businessResult = null;
    message = null;
    cause = null;
    LOCAL.remove();
    return this;
}

toString() 方法

@Override
public String toString() {
    StringBuilder description = new StringBuilder();

    // message
    if (this.message != null) {
        description.append(LINE_SEPARATOR);
        description.append("### ");
        description.append(this.message);
    }

    // request
    if (request != null) {
        description.append(LINE_SEPARATOR);
        description.append("### 回调参数:");
        description.append(this.request);
    }

    // orderCode
    if (orderCode != null) {
        description.append(LINE_SEPARATOR);
        description.append("### 订单号:");
        description.append(this.orderCode);
    }

    // check
    if(check != null ){
        description.append(LINE_SEPARATOR);
        description.append("### 验签结果:");
        description.append(this.check);
    }

    // queryOrderResult
    if(queryOrderResult != null ){
        description.append(LINE_SEPARATOR);
        description.append("### 订单查询结果:");
        description.append(this.queryOrderResult);
    }

    // payStatus
    if(payStatus != null ){
        description.append(LINE_SEPARATOR);
        description.append("### 订单支付状态:");
        description.append(this.payStatus);
    }

    // businessResult
    if(businessResult != null ){
        description.append(LINE_SEPARATOR);
        description.append("### 业务处理结果:");
        description.append(this.businessResult);
    }

    // cause
    if (cause != null) {
        description.append(LINE_SEPARATOR);
        description.append("### Cause:");
        description.append(this.cause);
    }

    return description.toString();
}

如何使用

NotifyCallBackController.java

@RequestMapping(value = "/notifyCallBack/{type}")
public void notifyCallBack(@PathVariable int type, HttpServletRequest request, HttpServletResponse response) throws Exception {
    try {
        handler(type,request,response);
    } catch (Exception e) {
        log.error(ErrorPayContext.instance().message(e.getMessage()).cause(e).toString());
        e.printStackTrace();
    } finally {
        ErrorPayContext.instance().reset();
        responseResult(response, "200");
    }
}

XXXService.java

···代码
ErrorPayContext.instance().request(回调信息)
···代码
ErrorPayContext.instance().check(验签结果)
···代码
ErrorPayContext.instance().queryOrderResult(订单查询结果)
···代码
ErrorPayContext.instance().payStatus(支付状态)
···代码
ErrorPayContext.instance().businessResult(处理业务的结果)

测试结果

android 支付宝支付完成回调app 支付回调失败怎么处理_java_02