若依项目集成sentinel、seata和shardingSphere

发布于:2025-05-23 ⋅ 阅读:(22) ⋅ 点赞:(0)

集成组件包括MySQL分库分表及读写分离、seata以及Sentinel

若依项目文档连接
代码下载地址

需要结合ruoyi代码配合看,前提是熟悉基本代码结构,熟悉feign调用和基础网关配置等。

采用的版本信息

<java.version>1.8</java.version>
<spring-boot.version>2.7.18</spring-boot.version>
<spring-cloud.version>2021.0.9</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.6.1</spring-cloud-alibaba.version>

项目目录结构

MYSQL分库分表及读写分离,版本基于MySQL8.0+版本

  1. 在ruoyi-file和ruoyi-system中引入ShardingSphere依赖,采用的是5.1.2版本
 <!-- ShardingSphere 读写分离/分库分表 -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.1.2</version>
</dependency>
  1. 在bootstrap.yaml中配置
    ruoyi-system,中有分表操作,对应
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    props:
      sql-show: true
    datasource:
      names: master,slave
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.0.177:3306/ry-user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: xxxx
        password: xxxx
      slave:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.0.179:3306/ry-user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: xxxx
        password: xxxxx
    rules:
      readwrite-splitting:
        data-sources:
          rw_ds:
            type: Static
            props:
              write-data-source-name: master
              read-data-source-names: slave
            load-balancer-name: round_robin
        load-balancers:
          round_robin:
            type: ROUND_ROBIN
      sharding:
        tables:
          lake_company_info:
            actual-data-nodes: rw_ds.xx_sd,rw_ds.xxx_ah,rw_ds.xxx_js,rw_ds.xxx_other
            table-strategy:
              standard:
                sharding-column: init_province_id
                sharding-algorithm-name: province-algorithm
        sharding-algorithms:
          province-algorithm:
            type: CLASS_BASED
            props:
              strategy: standard
              algorithmClassName: com.ruoyi.system.shardingconfig.ProvinceIdShardingAlgorithm

涉及到的类

package com.ruoyi.system.shardingconfig;

import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Properties;


//sharding分表规则
@Slf4j
public class ProvinceIdShardingAlgorithm implements StandardShardingAlgorithm<String> {


    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
        StringBuilder resultTableName = new StringBuilder();
        String logicTableName = preciseShardingValue.getLogicTableName();
        String value = preciseShardingValue.getValue();
        String postTable = "other" ;
        if("370000".equals(value)) {
            postTable = "sd" ;
        }
        if("340000".equals(value)) {
            postTable = "ah" ;
        }
        if("320000".equals(value)) {
            postTable = "js" ;
        }
        resultTableName.append(logicTableName)
                .append("_").append(postTable);
        log.error("操作的表名{}",resultTableName);
        return ShardingAlgorithmTool.shardingTablesCheckAndCreatAndReturn(logicTableName, resultTableName.toString());
    }

    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<String> rangeShardingValue) {
        return null;
    }

    @Override
    public Properties getProps() {
        return null;
    }

    @Override
    public void init(Properties properties) {
        System.out.println();
    }
}
package com.ruoyi.system.shardingconfig;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

@Slf4j
public class ShardingAlgorithmTool {
    private static final HashSet<String> tableNameCache = new HashSet<>();

//    private static HssHistoryMapper hssHistoryMapper=SpringUtil.getBean(HssHistoryMapper.class);

    /**
     * 判断 分表获取的表名是否存在 不存在则自动建表
     *
     * @param logicTableName  逻辑表名(表头)
     * @param resultTableName 真实表名
     * @return 确认存在于数据库中的真实表名
     */
    public static String shardingTablesCheckAndCreatAndReturn(String logicTableName, String resultTableName) {
        synchronized (logicTableName.intern()) {
            // 缓存中有此表 返回
            if (tableNameCache.contains(resultTableName)) {
                return resultTableName;
            }
            // 缓存中无此表 建表 并添加缓存
            // 调用mapper 创建表
            // @Update("CREATE TABLE IF NOT EXISTS ${name} LIKE hss_history")
//            hssHistoryMapper.createTable(resultTableName);
            tableNameCache.add(resultTableName);
        }
        return resultTableName;
    }

    /**
     * 缓存重载方法
     */
    public static void tableNameCacheReload() {
        // 读取数据库中所有表名
        List<String> tableNameList = getAllTableNameBySchema();
        // 删除旧的缓存(如果存在)
        ShardingAlgorithmTool.tableNameCache.clear();
        // 写入新的缓存9
        ShardingAlgorithmTool.tableNameCache.addAll(tableNameList);
    }

    /**
     * 获取数据库中的表名
     */
    public static List<String> getAllTableNameBySchema() {
        List<String> res = new ArrayList<>();
        // 获取数据中的表名,需要自己构建数据源 SHOW TABLES like 'hss_history%'
//         List<String> res = hssHistoryMapper.showTables();
//        Environment env = SpringUtil.getApplicationContext().getEnvironment();
//        try (Connection connection = DriverManager.getConnection(env.getProperty("spring.datasource.druid.url"), env.getProperty("spring.datasource.druid.username"), env.getProperty("spring.datasource.druid.password"));
//             Statement st = connection.createStatement()) {
//            try (ResultSet provinceRs = st.executeQuery("SHOW TABLES like 'lake_company_info%'")) {
//                while (provinceRs.next()) {
//                    res.add(provinceRs.getString(1));
//                }
//            }
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        res.add("lake_company_info");
        res.add("lake_company_info_sd");
        res.add("lake_company_info_ah");
        res.add("lake_company_info_js");
        res.add("lake_company_info_other");
        return res;
    }

    /**
     * 获取缓存中的表名
     * @return
     */
    public static HashSet<String> cacheTableNames() {
        return tableNameCache;
    }
}

package com.ruoyi.system.shardingconfig;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 项目启动后 读取已有分表 进行缓存
 */
@Slf4j
@Order(value = 1) // 数字越小 越先执行
@Component
public class ShardingTablesLoadRunner implements CommandLineRunner {
    @Override
    public void run(String... args) {
        ShardingAlgorithmTool.tableNameCacheReload();
    }
}

ruoyi-file中引入

spring:
  main:
    allow-bean-definition-overriding: true
  application:
    # 应用名称
    name: ruoyi-file
  profiles:
    # 环境配置
    active: dev
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 192.168.0.227:8848
      config:
        # 配置中心地址
        server-addr: 192.168.0.227:8848
        # 配置文件格式
        file-extension: yml
        # 共享配置
        shared-configs:
          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
  shardingsphere:
    props:
      sql-show: true
    datasource:
      names: master,slave
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.0.177:3306/seata_file?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: xxxx
        password: xxxx
      slave:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.0.179:3306/seata_file?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: xxxxx
        password: xxxxxx
    rules:
      readwrite-splitting:
        data-sources:
          rw_ds:
            type: Static
            props:
              write-data-source-name: master
              read-data-source-names: slave
            load-balancer-name: round_robin
        load-balancers:
          round_robin:
            type: ROUND_ROBIN
  1. 测试
    可以自行测试,可以正确看到写入走master,读取操作走slave
    在这里插入图片描述

sentinel集成 ,版本采用的是1.7.2

sentinel搭建参考连接

项目集成
先配置网关请求路由限流策略,在ruoyi-gateway中引入依赖

<!-- SpringCloud Alibaba Sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- SpringCloud Alibaba Sentinel Gateway -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

<!-- Sentinel Datasource Nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

在bootstrap中引入

spring: 
  application:
    # 应用名称
    name: ruoyi-gateway
  profiles:
    # 环境配置
    active: dev
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 192.168.0.227:8848
      config:
        # 配置中心地址
        server-addr: 192.168.0.227:8848
        # 配置文件格式
        file-extension: yml
        # 共享配置
        shared-configs:
          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
    sentinel:
      # 取消控制台懒加载
      eager: true
      transport:
        # 控制台地址
        dashboard: 192.168.0.172:8080
      # nacos配置持久化
      datasource:
        ds1:
          nacos:
            server-addr: 192.168.0.227:8848
            dataId: sentinel-ruoyi-gateway
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: gw-flow

nacos上的配置

具体内容如下:

[
    {
        "resource": "ruoyi-auth",
        "count": 500,
        "grade": 1,
        "limitApp": "default",
        "strategy": 0,
        "controlBehavior": 0
    },
	{
        "resource": "ruoyi-system",
        "count": 1000,
        "grade": 1,
        "limitApp": "default",
        "strategy": 0,
        "controlBehavior": 0
    },
	{
        "resource": "ruoyi-gen",
        "count": 200,
        "grade": 1,
        "limitApp": "default",
        "strategy": 0,
        "controlBehavior": 0
    },
	{
        "resource": "ruoyi-job",
        "count": 300,
        "grade": 1,
        "limitApp": "default",
        "strategy": 0,
        "controlBehavior": 0
    }
]

在网关项目中建立对应的类

package com.ruoyi.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.ruoyi.gateway.handler.SentinelFallbackHandler;

/**
 * 网关限流配置
 * 
 * @author ruoyi
 */
@Configuration
public class GatewayConfig
{
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelFallbackHandler sentinelGatewayExceptionHandler()
    {
        return new SentinelFallbackHandler();
    }
}

自定义限流异常处理类

package com.ruoyi.gateway.handler;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.ruoyi.common.core.utils.ServletUtils;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

/**
 * 自定义限流异常处理
 *
 * @author ruoyi
 */
public class SentinelFallbackHandler implements WebExceptionHandler
{
    private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
    {
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试");
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex)
    {
        if (exchange.getResponse().isCommitted())
        {
            return Mono.error(ex);
        }
        if (!BlockException.isBlockException(ex))
        {
            return Mono.error(ex);
        }
        return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
    }

    private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable)
    {
        return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
    }
}

运行前后端项目,访问对应接口。
在这里插入图片描述
之后对具体项目进行限流,在ruoyi-system中实操。同样的在ruoyi-system中引入依赖

<!-- SpringCloud Alibaba Sentinel -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<!-- Sentinel Datasource Nacos -->
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

在bootstrap文件中创建对应的限流和降级策略

spring:
  application:
    # 应用名称
    name: ruoyi-system
  profiles:
    # 环境配置
    active: dev
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 192.168.0.227:8848
      config:
        # 配置中心地址
        server-addr: 192.168.0.227:8848
        # 配置文件格式
        file-extension: yml
        # 共享配置
        shared-configs:
          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
    sentinel:
      eager: true
      transport:
        dashboard: 192.168.0.172:8080
      datasource:
        flow:
          nacos:
            server-addr: 192.168.0.227:8848
            dataId: ruoyi-system-flow-rules
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow
        degrade:
          nacos:
            server-addr: 192.168.0.227:8848
            dataId: ruoyi-system-degrade-rules
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: degrade
      enabled: true

在nacos上分别建立对应的文件
在这里插入图片描述
在这里插入图片描述

对应文件内容如下
ruoyi-system-flow-rules

[
    {
        "app": "ruoyi-system",  //对应应用
        "clusterMode": false,  // 是否集群模式
        "controlBehavior": 0,  // 流控效果(0:快速失败、1:warm up 2:排队等待)
        "count": 10,  // 阈值(QPS=2)
        "gmtModified": 1747376424368, 
        "grade": 1,  // 限流类型 (1:QPS,2:线程数)
        "id": 40, 
        "limitApp": "default", // 限流应用(default表示不区分来源)
        "resource": "dictList",  // 资源名称 (@SentinelResuorce的value)
        "strategy": 0 // 流控模式(0:直接 1:关联 2:链路)
    }
]

ruoyi-system-degrade-rules

[
  {
    "resource": "dictList",  // 资源名称
    "grade": 0,  // 0慢调用比例,1异常比例,2异常数
    "count": 23, // 阈值(RT=23ms)
    "timeWindow": 1, // 熔断恢复时间(秒)
    "minRequestAmount": 5, //最小请求数(触发熔断的最小请求)
    "statIntervalMs": 1000, // 统计窗口(毫秒)
    "slowRatioThreshold": 0.5 // 慢调用比例阈值(仅 grade=0 时生效)
  }
]

在代码中进行集成

RequiresPermissions("system:dict:list")
    @GetMapping("/list")
    @SentinelResource(value = "dictList", blockHandler = "selectUserByNameBlockHandler", fallback = "selectUserByNameFallback")
    public TableDataInfo list(SysDictType dictType)
    {
        startPage();
        R<Boolean> booleanR = this.remoteLogService.saveLogA("1", SecurityConstants.INNER);
        System.out.println(JSONObject.toJSONString(booleanR));
//        int x=1/0 ;
        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
        return getDataTable(list);
    }

    // 服务流量控制处理,触发走这个
    public TableDataInfo selectUserByNameBlockHandler(SysDictType dictType, BlockException ex)
    {
        System.out.println("selectUserByNameBlockHandler异常信息:" + ex.getMessage());
        return getDataTable(new ArrayList<>());
    }

    // 服务熔断降级处理,函数签名与原函数一致或加一个 Throwable 类型的参数  抛出异常走这个
    public TableDataInfo selectUserByNameFallback(SysDictType dictType, Throwable throwable)
    {
        System.out.println("selectUserByNameFallback异常信息:" + throwable.getMessage());
        return getDataTable(new ArrayList<>());
    }

以上代码会触发对应的规则,需要注意必须为public ,返回值必须一致,请求参数必须一致,熔断和流量控制的参数为额外传递一个Throwable throwable和 BlockException ex。如果想定义在某个类中,可以blockHandlerclass和fallbackclass并在类中定义不同的方法。

feign的集成

package com.ruoyi.system.api;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysOperLog;
import com.ruoyi.system.api.factory.RemoteLogFallbackFactory;
import org.springframework.web.bind.annotation.RequestParam;

import javax.validation.Valid;

/**
 * 日志服务
 * 
 * @author ruoyi
 */
@FeignClient(contextId = "remoteLogService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteLogFallbackFactory.class)
public interface RemoteLogService
{
    /**
     * 保存系统日志
     *
     * @param sysOperLog 日志实体
     * @param source 请求来源
     * @return 结果
     */
    @PostMapping("/operlog")
    public R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog, @RequestHeader(SecurityConstants.FROM_SOURCE) String source) throws Exception;

    /**
     * 保存访问记录
     *
     * @param sysLogininfor 访问实体
     * @param source 请求来源
     * @return 结果
     */
    @PostMapping("/logininfor")
    public R<Boolean> saveLogininfor(@RequestBody SysLogininfor sysLogininfor, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);

    /**
     * 测试使用
     * @param aa
     * @param source
     * @return
     */
    @PostMapping("/operlog/a")
    public R<Boolean> saveLogA(@RequestParam(value = "aa")String aa, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

服务不可用时会走这个类,RemoteLogFallbackFactory

package com.ruoyi.system.api.factory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.RemoteLogService;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysOperLog;

/**
 * 日志服务降级处理
 * 
 * @author ruoyi
 */
@Component
public class RemoteLogFallbackFactory implements FallbackFactory<RemoteLogService>
{
    private static final Logger log = LoggerFactory.getLogger(RemoteLogFallbackFactory.class);

    @Override
    public RemoteLogService create(Throwable throwable)
    {
        log.error("日志服务调用失败:{}", throwable.getMessage());
        return new RemoteLogService()
        {
            @Override
            public R<Boolean> saveLog(SysOperLog sysOperLog, String source)
            {
                return R.fail("保存操作日志失败:" + throwable.getMessage());
            }

            @Override
            public R<Boolean> saveLogininfor(SysLogininfor sysLogininfor, String source)
            {
                return R.fail("保存登录日志失败:" + throwable.getMessage());
            }

            @Override
            public R<Boolean> saveLogA(String aa, String source) {
                return R.fail("保存操作日志失败:" + throwable.getMessage());
            }
        };

    }
}

以上代码结合ruoyi框架,可以自行模拟,在下游服务不可用时,会走降级方法。

sentinel集成 ,版本采用的是1.4.x

搭建参考连接

在项目中集成,使用ruoyi-system和ruoyi-file

先引入依赖
ruoyi-system

<!-- SpringBoot Seata -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

ruoyi-file

<!-- SpringBoot Seata -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

在ruoyi-system的bootstrap中引入

# seata配置
seata:
  enabled: true
  # Seata 应用编号,默认为 ${spring.application.name}
  application-id: ${spring.application.name}
  # Seata 事务组编号,用于 TC 集群名
  tx-service-group: my_test_tx_group # 要和nacos配置文件中一致
  # 关闭自动代理
  enable-auto-data-source-proxy: false
  # 服务配置项
  service:
    # 虚拟组和分组的映射
    vgroup-mapping:
      my_test_tx_group: default # 要和nacos配置文件中一致
    grouplist:
      default: 192.168.0.172:8091 #seata启动地址
  config:
    type: nacos
    nacos:
      serverAddr: 192.168.0.227:8848
      group: SEATA_GROUP
      namespace: seata
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.0.227:8848
      namespace: seata

在ruoyi-file的bootstrap中引入

seata:
  enabled: true
  # Seata 应用编号,默认为 ${spring.application.name}
  application-id: ${spring.application.name}
  # Seata 事务组编号,用于 TC 集群名
  tx-service-group: my_test_tx_group
  # 关闭自动代理
  enable-auto-data-source-proxy: false
  # 服务配置项
  service:
    # 虚拟组和分组的映射
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 192.168.0.172:8091
  config:
    type: nacos
    nacos:
      serverAddr: 192.168.0.227:8848
      group: SEATA_GROUP
      namespace: seata
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.0.227:8848
      namespace: seata

因为集成了ShardingSphere,所以需要额外配置代理数据源,该配置在两个项目中都要引入

package com.ruoyi.file.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class DataSourceProxyConfig {
//    @Bean
//    @ConfigurationProperties(prefix = "spring.datasource")
//    public DataSource druidDataSource() {
//        return new DruidDataSource();
//    }

    @Primary
    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }
}

以上就配置完成,接下来进行测试。
点击新增菜单按钮,调用后端接口

 @RequiresPermissions("system:menu:add")
    @Log(title = "菜单管理", businessType = BusinessType.INSERT)
    @PostMapping
//    @Transactional
    @GlobalTransactional(rollbackFor = Exception.class)
    public AjaxResult add(@Validated @RequestBody SysMenu menu)
    {
        if (!menuService.checkMenuNameUnique(menu))
        {
            return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        }
        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
        {
            return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
        }
        menu.setCreateBy(SecurityUtils.getUsername());

        log.info("当前 XID: {}", RootContext.getXID());

        SysFileInfo fileInfo = new SysFileInfo();
        fileInfo.setFileName("11111111111");
        fileInfo.setFilePath("222222");


        AjaxResult ajaxResult = toAjax(menuService.insertMenu(menu));
		// 调用远程服务
        R<Boolean> booleanR = this.remoteFileService.saveFile(fileInfo);
        // 要根据上面的爆出对应的异常信息
        log.error(JSONObject.toJSONString(booleanR));
        if(booleanR.getCode() == 500) {
            throw new RuntimeException("巴伯错");
        }
//        int x=1/0;

        return ajaxResult ;
    }

ruoyi-file中的内容

package com.ruoyi.file.service;

import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.file.mapper.SysFileInfoMapper;
import com.ruoyi.system.api.domain.SysFileInfo;
import io.seata.core.context.RootContext;

@Service
public class SysFileInfoServiceImpl implements ISysFileInfoService
{
    private static final Logger log = LoggerFactory.getLogger(SysFileInfoServiceImpl.class);

    @Resource
    private SysFileInfoMapper sysFileInfoMapper;

    /**
     * 事务传播特性设置为 REQUIRES_NEW 开启新的事务 重要!!!!一定要使用REQUIRES_NEW
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertFile(SysFileInfo fileInfo)
    {
        fileInfo.setFileId(System.currentTimeMillis()+"");
        log.info("=============FILE START=================");
        log.info("当前 XID: {}", RootContext.getXID());

        sysFileInfoMapper.insert(fileInfo);
        int x=1/0;
        log.info("=============FILE END=================");
    }

}

在调用远程服务 在新增菜单成功后,远程调用 this.remoteFileService.saveFile(fileInfo) 保存时出错。代码回滚成功,此时有一个注意事项,在下游服务报错,回传了降级方法内容或者全局异常时,需要在上游服务中抛出,要不然不能够正常处理。

还有一种情况,远程服务调用成功,在上游服务中报错,此时也会正常回滚。

 @RequiresPermissions("system:menu:add")
    @Log(title = "菜单管理", businessType = BusinessType.INSERT)
    @PostMapping
//    @Transactional
    @GlobalTransactional(rollbackFor = Exception.class)
    public AjaxResult add(@Validated @RequestBody SysMenu menu)
    {
        if (!menuService.checkMenuNameUnique(menu))
        {
            return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        }
        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
        {
            return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
        }
        menu.setCreateBy(SecurityUtils.getUsername());

        log.info("当前 XID: {}", RootContext.getXID());

        SysFileInfo fileInfo = new SysFileInfo();
        fileInfo.setFileName("11111111111");
        fileInfo.setFilePath("222222");

	   // 调用远程服务
        R<Boolean> booleanR = this.remoteFileService.saveFile(fileInfo);
          // 要根据上面的爆出对应的异常信息
        log.error(JSONObject.toJSONString(booleanR));
        if(booleanR.getCode() == 500) {
            throw new RuntimeException("巴伯错");
        }
        int x=1/0;

        return  toAjax(menuService.insertMenu(menu));
    }

ruoyi-file中的内容

package com.ruoyi.file.service;

import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.file.mapper.SysFileInfoMapper;
import com.ruoyi.system.api.domain.SysFileInfo;
import io.seata.core.context.RootContext;

@Service
public class SysFileInfoServiceImpl implements ISysFileInfoService
{
    private static final Logger log = LoggerFactory.getLogger(SysFileInfoServiceImpl.class);

    @Resource
    private SysFileInfoMapper sysFileInfoMapper;

    /**
     * 事务传播特性设置为 REQUIRES_NEW 开启新的事务 重要!!!!一定要使用REQUIRES_NEW
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertFile(SysFileInfo fileInfo)
    {
        fileInfo.setFileId(System.currentTimeMillis()+"");
        log.info("=============FILE START=================");
        log.info("当前 XID: {}", RootContext.getXID());

        sysFileInfoMapper.insert(fileInfo);
        log.info("=============FILE END=================");
    }

}

代码基本完成了,可能还有其他类型的,也基本差不多,遵循对应的规则就可以。