package com.hdkj.lt.core.plugins.db;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.*;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

/**
 * @Description: 本地事务管理，在下面切入点中的方法按照指定的名称命名即可，类和方法上无需添加@Transactional注解
 * @Author: wenhao
 * @Create: 2022/05/27 13:47
 * @Version: 1.0
 */
@Configuration(proxyBeanMethods = false)
public class LocalTransactionAdvice {

    private static final String TRANSACTION_INTERCEPTOR_NAME = "globalTransactionInterceptor";

    private static final String TRANSACTION_POINTCUT_ADVISOR_NAME = "globalTransactionPointcutAdvisor";

    private static final String DEFAULT_DATA_SOURCE_TRANSACTION_MANAGER_NAME = "transactionManager";

    // 事务的超时时间
    private static final int TX_TIMEOUT = 60 * 60;

    // 切入点表达式，配置哪些包下的哪些类的方法需要使用事务
    private static final String AOP_POINTCUT_EXECUTION = "execution(* com.hdkj.lt..service..*.*(..))";

    // 涉及到事务的方法名
    private static final List<String> TRANSACTION_ATTRIBUTE_LIST = Lists.newArrayList("add*", "save*", "insert*",
            "create*", "submit*", "update*", "edit*", "modify*", "alter*", "upload*", "delete*", "remove*", "clean*", "recycle*",
            "batch*", "mark*", "disable*", "enable*", "handle*", "syn*", "reg*", "gen*", "*Tx"
    );

    // 无事务的方法名
    private static final String NO_TX = "noTx*";

    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;

    /**
     * 事务拦截器
     *
     * @return 事务拦截器对象
     */
    @Primary
    @Bean(name = TRANSACTION_INTERCEPTOR_NAME)
    @DependsOn(value = DEFAULT_DATA_SOURCE_TRANSACTION_MANAGER_NAME)
    public TransactionInterceptor globalTransactionInterceptor() {
        // 必须带事务，当前存在事务就使用当前事务，当前不存在事务就创建一个新的事务
        RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
        // 检查型异常也回滚
        requiredTx.setRollbackRules(Lists.newArrayList(new RollbackRuleAttribute(Throwable.class)));
        requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //事务的传播行为
        requiredTx.setTimeout(TX_TIMEOUT);
        // 设置方法对应的事务
        Map<String, TransactionAttribute> txNameMap = Maps.newHashMapWithExpectedSize(TRANSACTION_ATTRIBUTE_LIST.size() + 5);
        TRANSACTION_ATTRIBUTE_LIST.forEach((key) -> txNameMap.put(key, requiredTx));
        // 无事务地执行，挂起任何存在的事务
        RuleBasedTransactionAttribute noTx = new RuleBasedTransactionAttribute();
        noTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
        txNameMap.put(NO_TX, noTx);
        // 只读事务
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true); //是否只读
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
        // 除了上面的事物以外，都走只读事物
        txNameMap.put("*", readOnlyTx);
        NameMatchTransactionAttributeSource txTransactionAttributeSource = new NameMatchTransactionAttributeSource();
        txTransactionAttributeSource.setNameMap(txNameMap);
        return new TransactionInterceptor(dataSourceTransactionManager, txTransactionAttributeSource);
    }

    @Bean(name = TRANSACTION_POINTCUT_ADVISOR_NAME)
    public Advisor advisor(@Qualifier(value = TRANSACTION_INTERCEPTOR_NAME) TransactionInterceptor transactionInterceptor) {
        // 配置事务切入点表达式
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXECUTION);
        // 增强事务，关联切入点和事务属性
        return new DefaultPointcutAdvisor(pointcut, transactionInterceptor);
    }

}
