淘东电商项目(63) -聚合支付(多线程日志收集)
引言
本文代码已提交至Github(版本号:
e1ba173d44dfc6e5753f7c11b8bb93b34e1fa811
),有兴趣的同学可以下载来看看:https://github.com/ylw-github/taodong-shop
阅读本文前,有兴趣的同学可以参考我之前写的聚合支付的文章:
- 《淘东电商项目(52) -聚合支付开篇》
- 《淘东电商项目(53) -银联支付案例源码分析》
- 《淘东电商项目(54) -银联支付案例(同步与异步)》
- 《淘东电商项目(55) -支付系统核心表设计》
- 《淘东电商项目(56) -支付系统分布式事务的解决方案》
- 《淘东电商项目(57) -聚合支付(支付令牌接口)》
- 《淘东电商项目(58) -聚合支付(基于设计模式自动跳转支付接口)》
- 《淘东电商项目(59) -聚合支付(集成银联支付)》
- 《淘东电商项目(60) -聚合支付(集成支付宝)》
- 《淘东电商项目(61) -聚合支付(基于模板方法设计模式管理支付回调)》
- 《淘东电商项目(62) -聚合支付(基于模板方法设计模式管理支付回调-支付宝)》
目前「淘东电商项目」的聚合支付模块,已经完成了银联支付以及支付宝的集成,而且实现了基于"模板设计模式"的方式来管理银联支付
以及支付宝
结果回调接口。
在回调接口中,前面的代码是没有实现写日志的功能的,由于写入日志需要时间,所以要使用多线程处理,本文来主要讲解下核心的代码。
本文目录结构:
l____引言
l____ 1. 写支付结果日志核心代码
l____ 2. 测试
1. 写支付结果日志核心代码
先来看看前面写的支付回调的模板方法类:
/**
* description: 使用模版方法重构异步回调代码
* create by: YangLinWei
* create time: 2020/5/18 9:34 上午
*/
@Slf4j
public abstract class AbstractPayCallbackTemplate {
/**
* 获取所有请求的参数,封装成Map集合 并且验证是否被篡改
*
* @param req
* @param resp
* @return
*/
public abstract Map<String, String> verifySignature(HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, AlipayApiException;
/**
* description: 同步回调执行业务逻辑
* param:
*
* @return
*/
public abstract String syncService(HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, AlipayApiException;
/**
* 异步回调执行业务逻辑
*
* @param verifySignature
*/
public abstract String asyncService(Map<String, String> verifySignature);
public abstract String failResult();
public abstract String successResult();
/**
* *1. 将报文数据存放到es <br>
* 1. 验证报文参数<br>
* 2. 将日志根据支付id存放到数据库中<br>
* 3. 执行的异步回调业务逻辑<br>
*/
public String asyncCallBack(HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, AlipayApiException {
// 1. 验证报文参数 相同点 获取所有的请求参数封装成为map集合 并且进行参数验证
Map<String, String> verifySignature = verifySignature(req, resp);
// 2.将日志根据支付id存放到数据库中
String paymentId = verifySignature.get("paymentId");
if (StringUtils.isEmpty(paymentId)) {
return failResult();
}
// 3.采用异步形式写入日志到数据库中
payLog(paymentId, verifySignature);
String result = verifySignature.get(PayConstant.RESULT_NAME);
// 4.201报文验证签名失败
if (result.equals(PayConstant.RESULT_PAYCODE_201)) {
return failResult();
}
// 5.执行的异步回调业务逻辑
return asyncService(verifySignature);
}
/**
* 采用多线程技术或者MQ形式进行存放到数据库中
*
* @param paymentId
* @param verifySignature
*/
private void payLog(String paymentId, Map<String, String> verifySignature) {
log.info(">>paymentId:{paymentId},verifySignature:{}", verifySignature);
}
}
可以看到第三步的异步写入日志到数据库功能是没有完成的,那该如何实现异步呢?其实可以使用多线程,下面来看下项目核心的代码。
①首先在配置文件配置线程池的参数信息:
- CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务。
- IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数。
###多线程配置
threadPool:
###核心线程数
corePoolSize: 10
###最大线程数
maxPoolSize: 20
## 队列容量
queueCapacity: 16
②SpringBoot配置文件:
/**
* description: springboot 整合线程池
* create by: YangLinWei
* create time: 2020/5/18 2:39 下午
*/
@Configuration
@EnableAsync
@Slf4j
public class AsyncTaskConfig implements AsyncConfigurer {
/**
* 最小线程数(核心线程数)
*/
@Value("${threadPool.corePoolSize}")
private int corePoolSize;
/**
* 最大线程数
*/
@Value("${threadPool.maxPoolSize}")
private int maxPoolSize;
/**
* 等待队列(队列最大长度)
*/
@Value("${threadPool.queueCapacity}")
private int queueCapacity;
/**
* ThredPoolTaskExcutor的处理流程 当池子大小小于corePoolSize,就新建线程,并处理请求
* 当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去workQueue中取任务并处理
* 当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize,
* 就用RejectedExecutionHandler来做拒绝处理
* 当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理就自行销毁
*/
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// 最小线程数(核心线程数)
taskExecutor.setCorePoolSize(corePoolSize);
// 最大线程数
taskExecutor.setMaxPoolSize(maxPoolSize);
// 等待队列(队列最大长度)
taskExecutor.setQueueCapacity(queueCapacity);
taskExecutor.initialize();
return taskExecutor;
}
/**
* 异步异常处理
*
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SpringAsyncExceptionHandler();
}
class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
log.error("Exception occurs in async method", throwable.getMessage());
}
}
}
②异步写入日志,修改后的代码如下(注意第3步):
/**
* description: 使用模版方法重构异步回调代码
* create by: YangLinWei
* create time: 2020/5/18 9:34 上午
*/
@Slf4j
@Component
public abstract class AbstractPayCallbackTemplate {
@Autowired
private PaymentTransactionLogMapper paymentTransactionLogMapper;
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
/**
* 获取所有请求的参数,封装成Map集合 并且验证是否被篡改
*
* @param req
* @param resp
* @return
*/
public abstract Map<String, String> verifySignature(HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, AlipayApiException;
/**
* 同步回调执行业务逻辑
*
*/
public abstract String syncService(HttpServletRequest request, HttpServletResponse resp) throws UnsupportedEncodingException, AlipayApiException;
/**
* 异步回调执行业务逻辑
*
* @param verifySignature
*/
@Transactional
public abstract String asyncService(Map<String, String> verifySignature);
public abstract String failResult();
public abstract String successResult();
/**
* *1. 将报文数据存放到es <br>
* 1. 验证报文参数<br>
* 2. 将日志根据支付id存放到数据库中<br>
* 3. 执行的异步回调业务逻辑<br>
*/
@Transactional
public String asyncCallBack(HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, AlipayApiException {
// 1. 验证报文参数 相同点 获取所有的请求参数封装成为map集合 并且进行参数验证
Map<String, String> verifySignature = verifySignature(req, resp);
// 2.将日志根据支付id存放到数据库中
String paymentId = verifySignature.get("paymentId");
if (StringUtils.isEmpty(paymentId)) {
return failResult();
}
// 3.采用异步形式写入日志到数据库中
threadPoolTaskExecutor.execute(new PayLogThread(paymentId, verifySignature));
String result = verifySignature.get(PayConstant.RESULT_NAME);
// 4.201报文验证签名失败
if (result.equals(PayConstant.RESULT_PAYCODE_201)) {
return failResult();
}
// 5.执行的异步回调业务逻辑
return asyncService(verifySignature);
}
/**
* 采用多线程技术或者MQ形式进行存放到数据库中
*
* @param paymentId
* @param verifySignature
*/
private void payLog(String paymentId, Map<String, String> verifySignature) {
log.info(">>paymentId:{paymentId},verifySignature:{}", verifySignature);
PaymentTransactionLogEntity paymentTransactionLog = new PaymentTransactionLogEntity();
paymentTransactionLog.setTransactionId(paymentId);
paymentTransactionLog.setAsyncLog(verifySignature.toString());
paymentTransactionLogMapper.insertTransactionLog(paymentTransactionLog);
}
// A 1423 B 1234
/**
* 使用多线程写入日志目的:加快响应 提高程序效率 使用线程池维护线程
*/
class PayLogThread implements Runnable {
private String paymentId;
private Map<String, String> verifySignature;
public PayLogThread(String paymentId, Map<String, String> verifySignature) {
this.paymentId = paymentId;
this.verifySignature = verifySignature;
}
@Override
public void run() {
payLog(paymentId, verifySignature);
}
}
}
2. 测试
首先启动项目(Eureka注册中心、单点服务中心、会员服务、支付服务、支付门户):
②浏览器模拟提交订单:请求地址http://localhost:8600/cratePayToken?payAmount=99999&orderId=20200513141452&userId=27&productName=%E5%B9%BF%E4%B8%9C%E7%B1%B3%E9%85%92
数据库订单状态为未支付:
③模拟使用银联支付,浏览器请求:http://localhost:8079/pay?payToken=pay_6ccc4d754d664ce2bf43c3d14cc39187
④.点击银联支付,按照提示一步一步支付至完成:
可以看到订单状态为已支付:
同时也插入了日志:
相关文章
- java基础知识回顾之java Thread类学习(七)--java多线程通信等待唤醒机制(wait和notify,notifyAll)
- [posix]Posix多线程编程
- java实现多线程(车站卖票)
- C++ 多线程(两个线程卖火车票)
- java复习-多线程
- c# 多线程传值注意的地方
- java多线程 -- 同步鎖
- android --多线程下载
- Java多线程学习笔记 - 十二、并发工具
- 目录 1. 数据库优化漏斗法则1 2. 常见优化手段2 2.1. 索引2 2.2. 分页 只返回需要的字段2 2.3. 批处理2 2.4. 其他 sp 多线程等2 3. 索引类型 n
- iOS 多线程和锁
- C++多线程编程超详解
- Java 多线程实现的四种方式
- Android FileDownloader框架多线程下载
- 多线程编程2-NSOperation
- JAVA实现多线程入门
- 009-多线程-基础-AbstractQueuedSynchronizer-用于构建锁和同步容器的框架、独占锁与共享锁的获取与释放
- 多线程的对照与案例(计算文件夹下文件的大小)
- python并发编程之多进程、多线程、异步和协程
- C++ 特性之多线程
- Java多线程中static变量的使用