SpringCloud + Nacos + Seata1.2.0 最新版

关于Seata配置, 一步一个坑, 配置过程中0.9.0~1.2.0版本全下载也没有成功(非Feign降级可以回滚, 但是数据库事务相关表没任何数据出现), 官方文档和Demo描述都是1.0.0之前老版本的, 1.0.0以上要么只有简单说明, 要么直接项目贴jar包, 终于是自己摸索出来了, 希望能让Springcloud整合Seata的同学少走点坑.
下面开始详细的配置说明.

版本信息

Jar版本信息

JDK : 1.8.0
Springboot: 2.2.6.RELEASE
Nacos: 2.2.1.RELEASE
OpenFeign: 2.2.2.RELEASE
MybatisPlus: 3.3.1.tmp

Nacos 及 Seata 版本

Nacos: 1.2.1
Seata: 1.2.0

项目相关其他Jar版本

Sentinel-dashboard: 1.7.2
spring-cloud-starter-alibaba-sentinel: 2.2.1.RELEASE
druid: 1.1.22

这里也不描述官方的转账Demo, 没什么用, 只要配置好了事务过程中Seata数据库有信息就是可用的.

Seata下载地址
Nacos跳过

解压后修改三个配置文件. file.conf, file.conf.example, registry.conf.

file.conf 修改mode=‘db’, 下方Seata数据库地址账号密码

## transaction log store, only used in seata-server
store {
  ## store mode: file、db
  mode = "db"

  ## file store property
  file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
  }

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "mysql"
    password = "mysql"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}

file.conf.example 修改mode=‘db’, 下方Seata数据库地址账号密码
transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = false
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThreadPrefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}

## transaction log store, only used in server side
store {
  ## store mode: file、db
  mode = "db"
  ## file store property
  file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
  }

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "mysql"
    password = "mysql"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
  }
}
## server configuration, only used in server side
server {
  recovery {
    #schedule committing retry period in milliseconds
    committingRetryPeriod = 1000
    #schedule asyn committing retry period in milliseconds
    asynCommittingRetryPeriod = 1000
    #schedule rollbacking retry period in milliseconds
    rollbackingRetryPeriod = 1000
    #schedule timeout retry period in milliseconds
    timeoutRetryPeriod = 1000
  }
  undo {
    logSaveDays = 7
    #schedule delete expired undo_log in milliseconds
    logDeletePeriod = 86400000
  }
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  maxCommitRetryTimeout = "-1"
  maxRollbackRetryTimeout = "-1"
  rollbackRetryTimeoutUnlockEnable = false
}

## metrics configuration, only used in server side
metrics {
  enabled = false
  registryType = "compact"
  # multi exporters use comma divided
  exporterList = "prometheus"
  exporterPrometheusPort = 9898
}
registry.conf registry.type=‘nacos’ 并标明nacos端口
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

启动Nacos, 启动Seata, 若Seata运行无报错且Nacos服务列表存在Seata服务则第一步成功.

Seata数据库配置, 创建表
-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `global_table` (
  `xid` varchar(128)  not null,
  `transaction_id` bigint,
  `status` tinyint not null,
  `application_id` varchar(32),
  `transaction_service_group` varchar(32),
  `transaction_name` varchar(128),
  `timeout` int,
  `begin_time` bigint,
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`xid`),
  key `idx_gmt_modified_status` (`gmt_modified`, `status`),
  key `idx_transaction_id` (`transaction_id`)
);

-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
  `branch_id` bigint not null,
  `xid` varchar(128) not null,
  `transaction_id` bigint ,
  `resource_group_id` varchar(32),
  `resource_id` varchar(256) ,
  `lock_key` varchar(128) ,
  `branch_type` varchar(8) ,
  `status` tinyint,
  `client_id` varchar(64),
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`branch_id`),
  key `idx_xid` (`xid`)
);

-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
  `row_key` varchar(128) not null,
  `xid` varchar(96),
  `transaction_id` long ,
  `branch_id` long,
  `resource_id` varchar(256) ,
  `table_name` varchar(32) ,
  `pk` varchar(36) ,
  `gmt_create` datetime ,
  `gmt_modified` datetime,
  primary key(`row_key`)
);

其他各服务数据库添加undo_log表
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

服务配置

Pom.xml 导入Jar. 每个服务都需导入Seata相关包
<properties>
   <java.version>1.8</java.version>
   <nacos.version>2.2.1.RELEASE</nacos.version>
   <boot.version>2.2.6.RELEASE</boot.version>
   <feign.version>2.2.2.RELEASE</feign.version>
</properties>

<!--Seata 包-->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
   <version>2.2.1.RELEASE</version>
   <exclusions>
       <exclusion>
           <groupId>io.seata</groupId>
           <artifactId>seata-all</artifactId>
       </exclusion>
       <exclusion>
           <groupId>io.seata</groupId>
           <artifactId>seata-spring-boot-starter</artifactId>
       </exclusion>
   </exclusions>
  </dependency>
  <dependency>
      <groupId>io.seata</groupId>
      <artifactId>seata-spring-boot-starter</artifactId>
      <version>1.2.0</version>
  </dependency>
  <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.22</version>
  </dependency>

<!--openfeign 支持解析MVC注解接口-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
      <version>${feign.version}</version>
  </dependency>
  <!--Nacos配置-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      <version>2.2.1.RELEASE</version>
  </dependency>
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
      <version>${nacos.version}</version>
  </dependency>
  <!--Sentinel 流量哨兵-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
      <version>${nacos.version}</version>
  </dependency>
  <!--Sentinel-Nacos动态配置-->
  <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-datasource-nacos</artifactId>
      <version>1.7.2</version>
  </dependency>
 <!--ORM-->
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.3.1.tmp</version>
     <exclusions>
         <exclusion>
             <groupId>com.github.jsqlparser</groupId>
             <artifactId>jsqlparser</artifactId>
         </exclusion>
     </exclusions>
 </dependency>
yml配置

项目使用Nacos动态配置, 我这里做了拆分
bootstrap优先级大于application, 所有拉取Nacos配置的要写在此处, 不然读取不到就会报错.
bootstrap.yml 主要是读取Nacos配置, 如下:

bootstrap.yml
spring:
  nacos-host: localhost
  nacos-port: 8848
  application:
    name: xxx-server
  cloud:
    nacos:
      discovery:
        server-addr: ${spring.nacos-host}:${spring.nacos-port}
      config:
        server-addr: ${spring.nacos-host}:${spring.nacos-port}
        file-extension: yaml
    #Sentinel-Nacos 配置
    sentinel:
      datasource:
        ds:
          nacos:
            server-addr: ${spring.nacos-host}:${spring.nacos-port}
            data-id: ${spring.application.name}-sentinel
            groupId: FLOW
            rule-type: flow

application.yml
spring:
  profiles:
    active: dev
application-dev.yml
server:
  port: 8002
spring:
  application:
    name: xxx-server # 应用名称 自定义
    host: localhost
  # druid 数据源配置
  datasource:
    url: jdbc:mysql://localhost:3308/xxx业务数据库?allowMultiQueries=true&autoReconnect=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useUnicode=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
    username: mysql
    password: mysql
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    filters: stat,wall,slf4j
    maxActive: 20
    initialSize: 5
    maxWait: 60000
    minIdle: 5
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

  main:
    allow-bean-definition-overriding: true
  # 连接控制台
  cloud:
    sentinel:
      transport:
        port: 7001
        dashboard: ${spring.application.host}:8080

mybatis-plus:
  global-config:
    db-config:
      id-type: auto
      # 表名前缀
      table-prefix: 'xxx'
  configuration:
    # 驼峰下划线映射
    map-underscore-to-camel-case: true
    cache-enabled: false
  #  config-location: classpath:mybatis/mybatis.cfg.xml
  mapper-locations: classpath:mapper/*.xml


# 开启Sentinel熔断器
feign:
  sentinel:
    enabled: true
# 饿加载开启 Feign 预加载, 防止第一次请求超时
ribbon:
  eager-load:
    enabled: true
    clients: xxx-server, xxx-server

seata:
  application-id: ${spring.application.name} # Seata 应用名称,默认使用 ${spring.application.name}
  tx-service-group: default # Seata 事务组, 高版本没找到相关配置, 是否可配置未知 选用默认default
  # 服务配置项
  service:
    # 虚拟组和分组的映射 1.0.0以上好像将vgroup-mapping 改为 vgroupMapping, 此处是否影响未测试
    vgroupMapping:
      # 此处Key对应 tx-service-group 的 Value, 此处 value 默认 default
      default: default
    # 分组和 Seata 服务的映射 默认端口8091
    grouplist:
      default: 127.0.0.1:8091

调用说明: A服务调用B服务, 两边都是insert, 在B服务插入成功后抛出异常, 测试是否回滚.

A服务启动类添加Feign注解, @EnableFeignClients, @MapperScan(“dao包路径”)

A服务编写业务代码, 使用MybatisPlus内置方法, 其他写法注入mapper接口调用方法就好.
主分支方法添加事务注解@GlobalTransactional(rollbackFor = Exception.class),
分支事务注解无需添加

package com.mis.rbac.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mis.common.constants.RespData;
import com.mis.common.rbac.entity.Dept;
import com.mis.common.utils.DateUtils;
import com.mis.rbac.dao.DeptMapper;
import com.mis.rbac.feign.BankServiceFeign;
import com.mis.rbac.service.DeptService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {

    @Autowired
    BankServiceFeign bankServiceFeign;

    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public RespData addDept() {
        Dept dept = new Dept();
        dept.setDeptName("asdasdsa");
        dept.setDeptPid(0L);
        dept.setCreateTime(DateUtils.getCurrentDate("", String.class));
        if(dept.insert()){
            bankServiceFeign.tran();
        }
        return RespData.success();
    }

}

Feign接口

package com.mis.rbac.feign;

import com.mis.common.constants.RespData;
import com.mis.rbac.feign.impl.BankServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;

@FeignClient(name = "firm-server"/*, fallbackFactory= BankServiceFallbackFactory.class*/)
public interface BankServiceFeign {

    @PostMapping("tran")
    public RespData tran();

}

Controller 跳过.

B服务
package com.mis.firm.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mis.common.constants.RespData;
import com.mis.common.firm.Bank;
import com.mis.firm.dao.BankMapper;
import com.mis.firm.service.BankService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BankServiceImpl extends ServiceImpl<BankMapper, Bank> implements BankService {

    @Override
    public RespData tran() throws Exception {
        System.out.println("执行添加Bank.");
        Bank bank = new Bank();
        bank.setBankAccount("123456");
        bank.setBankAddr("Transactional");
        bank.setBankName("事务");
        bank.setBankPhone("123456789");
        bank.setDel(0);
        if(bank.insert()){
            throw new Exception("手动异常, 事务回滚");
        }
        return RespData.success();
    }
}

启动Nacos, Seata后, 启动服务

若Seata控制台输出服务相关信息及数据库信息则连接成功, 如下:
在这里插入图片描述

若启动过程中出现 no available service ‘null’ found, please make sure registry config correct或DataSource错误,

说明未连接上Seata.
一般是Jar有冲突

spring-cloud-starter-alibaba-seata: 2.2.1.RELEASE 此版本与 seata-spring-boot-starter:1.1.0 冲突

更换为 seata-spring-boot-starter:1.2.0

其他情况: ClassNotFoundException:io.seata.spring.annotation.datasource.SeataDataSourceBeanPostProcessor
缺少Jar : seata-all
推荐使用seata-spring-boot-starter


启动成功并控制台无错误信息, 开始调用接口测试.

若主事务与分支事务均回滚, 则又进一步成功,

BUG断点测试, 拦截过程中查看Seata数据库表和服务undo_log表
若表存在数据, 如:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

则完成配置

Feign降级回滚(动态回滚)

使用Feign降级后, Seata不回滚.
需手动配置回滚
官方文档
在这里插入图片描述

RootContext.getXID() 获取当前XID后执行手动回滚
编写AOP, 切点事务注解所在的实现类, 各服务都添加此切面类

package com.mis.rbac.aop;

import java.lang.reflect.Method;

import com.mis.common.constants.RespData;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import lombok.extern.slf4j.Slf4j;

/**
 * Seata 动态事务回滚切面类 (Feign熔断降级回滚)
 */
@Slf4j
@Aspect
@Component
public class AtFeignTransactionalAop {

    @Before("execution(* com.mis.rbac.service.*.*(..))")
    public void before(JoinPoint joinPoint) throws TransactionException {
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        log.info("拦截到需要分布式事务的方法," + method.getName());
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
        // 超时时间 , 所在服务
        tx.begin(300000, "rbac-server");
        log.info("创建分布式事务id:{}", tx.getXid());
    }

    @AfterThrowing(throwing = "e", pointcut = "execution(* com.mis.rbac.service.*.*(..))")
    public void doRecoveryActions(Throwable e) throws TransactionException {
        log.info("方法执行异常:{}", e.getMessage());
        if (!StringUtils.isBlank(RootContext.getXID())) {
            log.info("分布式事务Id:{}, 手动回滚!", RootContext.getXID());
            GlobalTransactionContext.reload(RootContext.getXID()).rollback();
        }
    }

    @AfterReturning(value = "execution(* com.mis.rbac.service.*.*(..))", returning = "result")
    public void afterReturning(JoinPoint point, Object result) throws TransactionException {
        log.info("方法执行结束:{}", result);
        // 方法返回值 RespData是自定义的统一返回类
        RespData respData = (RespData)result;
        if (respData.getCode() != 0) {
            if (!StringUtils.isBlank(RootContext.getXID())) {
                log.info("分布式事务Id:{}, 手动回滚!", RootContext.getXID());
                GlobalTransactionContext.reload(RootContext.getXID()).rollback();
            }
        }
    }
}

统一异常
package com.mis.rbac.exception;


import com.mis.common.enums.Code;

/**
 * @ClassName GlobalException
 * @description: 自定义异常
 **/
public class GlobalException extends RuntimeException {

    /**
     * 异常状态码
     */
    private Integer code;

    /**
     * 异常信息
     */
    private String message;

    private String e;

    public GlobalException(Code enums){
        super();
        this.code=enums.getCode();
        this.message=enums.getMsg();
    }

    public GlobalException(Code enums, String e){
        super();
        this.code=enums.getCode();
        this.message=enums.getMsg();
        this.e = e;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getE() {
        return e;
    }

    public void setE(String e) {
        this.e = e;
    }
}

package com.mis.rbac.exception;

import com.mis.common.constants.RespData;
import com.mis.common.enums.Code;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

/**
 * @ClassName GlobalExceptionHandler
 * @Description TODO 全局异常处理类
 * @Version 1.0.0
 **/
@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(GlobalException.class)
    public RespData handlerGlobalException(GlobalException e) {
        e.printStackTrace();
        log.error("\r\n异常信息 : \r\n[\r\n 错误代码 >>>>>>> { "+ e.getCode() +" } , 错误信息 >>>>>>> { "+e.getE()+" } \r\n].");
        return RespData.error(Code.EXCEPTION_ERROR);
    }

    @ExceptionHandler(Exception.class)
    public  RespData handleException(Exception e) {
        e.printStackTrace();
        log.error("\r\n系统系统异常 >>>>>> { "+e.getMessage()+" } , \r\n错误信息 >>>>>>> {} ,\r\n { "+getExceptionAllinfo(e)+" } \r\n");
        return RespData.error(Code.EXCEPTION_ERROR);
    }

    /**
     * 验证异常
     * @param e
     * @return
     * @throws BindException
     */
    @ExceptionHandler(value = BindException.class)
    public RespData handleBindException(BindException e) {
        FieldError fieldError = e.getFieldError();
        StringBuilder sb = new StringBuilder();
        sb.append(fieldError.getField()).append(" =[ ").append(fieldError.getRejectedValue()).append(" ] ")
                .append(fieldError.getDefaultMessage());
        log.error("\r\n参数验证异常 >>>>>> 错误信息 : " + sb.toString());
        return RespData.error(-1,sb.toString());
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    public Object ConstraintViolationExceptionHandler(ConstraintViolationException exception){
        StringBuilder sb = new StringBuilder();
        int i = 0;
        for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
            if(i == 0){
                sb.append(violation.getMessageTemplate());
            }
            i++;
        }
        log.error("\r\n参数验证异常 >>>>>> 错误信息 : " + sb.toString() + "\r\n");
        return RespData.error(4000,sb.toString());
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Object MethodArgumentNotValidHandler(MethodArgumentNotValidException exception){
        StringBuilder sb = new StringBuilder();
        for (FieldError error : exception.getBindingResult().getFieldErrors()) {
            sb.append(error.getField()).append(" =[ ").append(error.getRejectedValue()).append(" ] ")
                    .append(error.getDefaultMessage());
        }
        log.error("\r\n参数验证异常 >>>>>> 错误信息 : " + sb.toString());
        return RespData.error(4000,sb.toString());
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public  RespData handleException(HttpRequestMethodNotSupportedException e) {
        System.err.println("\n************************************************************************************");
        System.err.println("\n*     请求异常 : ---> [ { "+e.getMessage()+" } ] .                                 *");
        System.err.println("\n************************************************************************************\n");
        return RespData.error(4000, e.getMessage());
    }


    public String getExceptionAllinfo(Exception ex) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream pout = new PrintStream(out);
        ex.printStackTrace(pout);
        String ret = new String(out.toByteArray());
        pout.close();
        try {
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return ret;
    }
}

动态回滚 已完成.

参考:
https://seata.io/zh-cn/blog/seata-spring-boot-aop-aspectj.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章