当前位置: 首页 > news >正文

若依项目集成sentinel、seata和shardingSphere

集成组件包括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: trueshardingsphere:props:sql-show: truedatasource:names: master,slavemaster:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.0.177:3306/ry-user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: xxxxpassword: xxxxslave:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.0.179:3306/ry-user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: xxxxpassword: xxxxxrules:readwrite-splitting:data-sources:rw_ds:type: Staticprops:write-data-source-name: masterread-data-source-names: slaveload-balancer-name: round_robinload-balancers:round_robin:type: ROUND_ROBINsharding:tables:lake_company_info:actual-data-nodes: rw_ds.xx_sd,rw_ds.xxx_ah,rw_ds.xxx_js,rw_ds.xxx_othertable-strategy:standard:sharding-column: init_province_idsharding-algorithm-name: province-algorithmsharding-algorithms:province-algorithm:type: CLASS_BASEDprops:strategy: standardalgorithmClassName: 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> {@Overridepublic 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());}@Overridepublic Collection<String> doSharding(Collection<String> collection, RangeShardingValue<String> rangeShardingValue) {return null;}@Overridepublic Properties getProps() {return null;}@Overridepublic 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();// 写入新的缓存9ShardingAlgorithmTool.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 {@Overridepublic void run(String... args) {ShardingAlgorithmTool.tableNameCacheReload();}
}

ruoyi-file中引入

spring:main:allow-bean-definition-overriding: trueapplication:# 应用名称name: ruoyi-fileprofiles:# 环境配置active: devcloud:nacos:discovery:# 服务注册地址server-addr: 192.168.0.227:8848config:# 配置中心地址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: truedatasource:names: master,slavemaster:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.0.177:3306/seata_file?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: xxxxpassword: xxxxslave:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.0.179:3306/seata_file?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: xxxxxpassword: xxxxxxrules:readwrite-splitting:data-sources:rw_ds:type: Staticprops:write-data-source-name: masterread-data-source-names: slaveload-balancer-name: round_robinload-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-gatewayprofiles:# 环境配置active: devcloud:nacos:discovery:# 服务注册地址server-addr: 192.168.0.227:8848config:# 配置中心地址server-addr: 192.168.0.227:8848# 配置文件格式file-extension: yml# 共享配置shared-configs:- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}sentinel:# 取消控制台懒加载eager: truetransport:# 控制台地址dashboard: 192.168.0.172:8080# nacos配置持久化datasource:ds1:nacos:server-addr: 192.168.0.227:8848dataId: sentinel-ruoyi-gatewaygroupId: DEFAULT_GROUPdata-type: jsonrule-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(), "请求超过最大数,请稍候再试");}@Overridepublic 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-systemprofiles:# 环境配置active: devcloud:nacos:discovery:# 服务注册地址server-addr: 192.168.0.227:8848config:# 配置中心地址server-addr: 192.168.0.227:8848# 配置文件格式file-extension: yml# 共享配置shared-configs:- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}sentinel:eager: truetransport:dashboard: 192.168.0.172:8080datasource:flow:nacos:server-addr: 192.168.0.227:8848dataId: ruoyi-system-flow-rulesgroupId: DEFAULT_GROUPdata-type: jsonrule-type: flowdegrade:nacos:server-addr: 192.168.0.227:8848dataId: ruoyi-system-degrade-rulesgroupId: DEFAULT_GROUPdata-type: jsonrule-type: degradeenabled: 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);@Overridepublic RemoteLogService create(Throwable throwable){log.error("日志服务调用失败:{}", throwable.getMessage());return new RemoteLogService(){@Overridepublic R<Boolean> saveLog(SysOperLog sysOperLog, String source){return R.fail("保存操作日志失败:" + throwable.getMessage());}@Overridepublic R<Boolean> saveLogininfor(SysLogininfor sysLogininfor, String source){return R.fail("保存登录日志失败:" + throwable.getMessage());}@Overridepublic 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: nacosnacos:serverAddr: 192.168.0.227:8848group: SEATA_GROUPnamespace: seataregistry:type: nacosnacos:application: seata-serverserver-addr: 192.168.0.227:8848namespace: 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: defaultgrouplist:default: 192.168.0.172:8091config:type: nacosnacos:serverAddr: 192.168.0.227:8848group: SEATA_GROUPnamespace: seataregistry:type: nacosnacos:application: seata-serverserver-addr: 192.168.0.227:8848namespace: 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@Beanpublic 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);@Resourceprivate 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);@Resourceprivate 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=================");}}

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

相关文章:

  • arcgispro双击打开没反应怎么办
  • 【华为OD- B卷 - 书籍叠放 200分(python、java、c、c++、js)】
  • 芯片分享之AD976性能介绍
  • CentOS Stream安装MinIO教程
  • 《探索具身智能机器人视觉-运动映射模型的创新训练路径》
  • Liquid Wire 柔性应变传感器:金属凝胶导体 | 仿生肌肉长度监测 | 高精度动作控制
  • 医学影像辅助诊断系统开发教程-基于tensorflow实现
  • 跨境外贸电商供应链一体化ERP管理系统
  • 高级SQL技巧:窗口函数与复杂查询优化实战
  • 龙虎榜——20250520
  • vform自定义表单研究
  • 数组day2
  • 【蓝桥杯嵌入式】【模块】五、ADC相关配置及代码模板
  • HarmonyOS5云服务技术分享--ArkTS开发Node环境
  • python八股文汇总(持续更新版)
  • LVS原理详解及LVS负载均衡工作模式
  • 【前端开发】Uniapp日期时间选择器:实现分钟动态步长设置
  • 深入解析Spring Boot与Kafka的集成实践
  • 密码学实验
  • 【Unity】DOTween的常用函数解释
  • 广西隆林发生一起山洪灾害,致4人遇难
  • 短剧植入,撬不动96.4%直男的钱包 | 调研报告
  • 离休干部周惠梅逝世,曾从事多年地下革命工作
  • 家国万里·时光故事会|从徐光启到徐家汇,一颗甘薯里的家国
  • “十五五”规划编制工作开展网络征求意见活动
  • 新华社原香港分社副社长、深圳市委原副书记秦文俊逝世