引言

本文代码已提交至Github(版本号:e1ba173d44dfc6e5753f7c11b8bb93b34e1fa811),有兴趣的同学可以下载来看看:https://github.com/ylw-github/taodong-shop

阅读本文前,有兴趣的同学可以参考我之前写的聚合支付的文章:

目前「淘东电商项目」的聚合支付模块,已经完成了银联支付以及支付宝的集成,而且实现了基于"模板设计模式"的方式来管理银联支付以及支付宝结果回调接口。

在回调接口中,前面的代码是没有实现写日志的功能的,由于写入日志需要时间,所以要使用多线程处理,本文来主要讲解下核心的代码。

本文目录结构:
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
在这里插入图片描述
④.点击银联支付,按照提示一步一步支付至完成:
在这里插入图片描述
可以看到订单状态为已支付:
在这里插入图片描述
同时也插入了日志:
在这里插入图片描述

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐