前言
第三方支付接过不?支付回调的代码写过不?
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报错了,问题很快就可以排查到
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(处理业务的结果)
测试结果