若依项目集成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+版本
- 在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>
- 在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
- 测试
可以自行测试,可以正确看到写入走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=================");}}
代码基本完成了,可能还有其他类型的,也基本差不多,遵循对应的规则就可以。
相关文章:
若依项目集成sentinel、seata和shardingSphere
集成组件包括MySQL分库分表及读写分离、seata以及Sentinel 若依项目文档连接 代码下载地址 需要结合ruoyi代码配合看,前提是熟悉基本代码结构,熟悉feign调用和基础网关配置等。 采用的版本信息 <java.version>1.8</java.version> <spr…...
张 推进对话式心理治疗:SOULSPEAK的聊天机器人
SOULSPEAK的聊天机器人 利用大语言模型(LLM)来提供低成本的心理治疗服务,旨在解决传统心理咨询在隐私、成本和可及性方面的不足。以下是核心内容的通俗解读: 1. 研究背景:传统心理治疗的困境 问题:全球心理健康问题日益严重(如焦虑、抑郁人数激增),但传统心理咨询受…...
java中的Filter使用详解
Filter(过滤器)是 Java Web 开发的核心组件之一,用于在请求到达 Servlet 或响应返回客户端之前进行拦截和处理。以下是其核心功能、使用方法和实际场景的详细解析: 一、Filter 的作用与原理 核心作用 Filter 充当请求与响应之间的…...
BERT 作为Transformer的Encoder 为什么采用可学习的位置编码
摘要 BERT 在位置编码上与原始 Transformer 论文中的 sin/cos 公式不同,选择了可学习(learned)的位置嵌入方案。本文将从 Transformer 原始位置编码选项入手,分析 BERT 选择 learned positional embeddings 的四大核心原因&#x…...
Vue百日学习计划Day43-45天详细计划-Gemini版
Day 43: Composable 函数基础与抽取简单逻辑 (~3 小时) 本日目标: 理解 Composable 函数的概念、优势,并学会如何将简单的、无状态的逻辑抽取为 Composable。所需资源: Vue 3 官方文档 (组合式函数): https://cn.vuejs.org/guide/reusability/composables.html 学…...
Kotlin 协程 (二)
Kotlin 协程提供了丰富的功能,能够高效地处理并发和异步任务。以下是对 Kotlin 协程中常见概念和功能的详细讲解,包括它们的定义、作用、使用场景以及最佳实践。 1. 协程核心概念 1.1 CoroutineScope 定义:CoroutineScope 是协程作用域的抽…...
Linux 下 rsync 工具详解与实用指南
Linux 下 rsync 工具详解与实用指南 一、什么是 rsync? rsync(remote sync)是 Linux/Unix 系统下常用的数据同步和备份工具。它可以高效地在本地与远程主机之间同步文件和目录,支持增量同步、断点续传、权限保留等功能ÿ…...
2025年医美行业报告60+份汇总解读 | 附 PDF 下载
原文链接:https://tecdat.cn/?p42122 医美行业在消费升级与技术迭代的双重驱动下,已从边缘市场逐步走向主流。数据显示,2024 年中国医美市场规模突破 3000 亿元,年复合增长率达 15%,但行业仍面临正品率不足、区域发展…...
汉得集星獭1.8.0正式发布,高效集成再赋能!
汉得企业级系统集成平台 (中文名集星獭,英文名JeeStar)1.8.0版本于2025年4月正式发布 。 集星獭是一款一站式多系统集成、多云集成、多端集成、多协议集成、多设备集成、数据集成、页面集成的全域集成解决方案产品。 此次发布主要聚焦于以下…...
一文深度解析:Pump 与 PumpSwap 的协议机制与技术差异
在 Solana 链上,Pump.fun 和其延伸产品 PumpSwap 构成了 meme coin 发行与流通的两大核心场景。从初期的游戏化发行模型,到后续的自动迁移与交易市场,Pump 系列协议正在推动 meme coin 从“爆发性投机”走向“协议化运营”。本文将从底层逻辑…...
数据库实验——备份与恢复
一、目的(本次实验所涉及并要求掌握的知识点) 1.掌握SQL server的备份与恢复 二、实验内容与设计思想(设计思路、主要数据结构、主要代码结构、主要代码段分析) 验证性实验 实验1:在资源管理器中建立备份设备实验 …...
抓包分析工具与流量监控软件
目录 一、抓包分析工具:定位问题的“放大镜” 1.1 工作原理简述 1.2 主流工具盘点 1.3 抓包的实战应用 二、流量监控软件:网络全景的“雷达系统” 2.1 功能特征 2.2 常用工具概览 2.3 实战应用场景 五、结语:深入可见,安…...
Go语言实战:使用 excelize 实现多层复杂Excel表头导出教程
Go 实现支持多层复杂表头的 Excel 导出工具 目录 项目介绍依赖说明核心结构设计如何支持多层表头完整使用示例总结与扩展 项目介绍 在实际业务系统中,Excel 文件导出是一项常见功能,尤其是报表类需求中常见的复杂多级表头,常规表格组件往…...
【算法】定长滑动窗口5.20
定长滑动窗口算法: 算法思路 滑动窗口遍历字符串:窗口大小为 k ,遍历字符串每个字符,维护窗口内元音字母数量。 统计窗口内元音:当字符是元音(a/e/i/o/u)时,计数器 vowel 加 1。…...
Java操作Elasticsearch 之 [Java High Level REST Clientedit]
<a name"VbjtD"></a> 1. 简述 Elasticsearch 是基于 Lucene 开发的一个分布式全文检索框架,向 Elasticsearch 中存储和从 Elasticsearch 中查询,格式是json。向 Elasticsearch 中存储数据,其实就是向 es 中的 index 下…...
数据集划分与格式转换:从原始数据到模型训练的关键步骤
在计算机视觉项目中,数据集的合理划分和格式转换是实现高效模型训练的基础。本文将详细介绍如何将图片和标注数据按比例切分为训练集和测试集,以及常见的数据格式转换方法,包括 JSON 转 YOLO 格式和 XML 转 TXT 格式。 一、将图片和标注数据…...
MinerU
简介 MinerU 是一款功能全面的文档处理系统,旨在将 PDF 和其他文档格式转换为机器可读的格式,例如 Markdown 和 JSON。该系统专注于在保留文档结构的同时,准确提取文档内容,处理复杂的布局,并转换公式和表格等特殊元素…...
Vue百日学习计划Day46-48天详细计划-Gemini版
Day 46: <KeepAlive> - 组件缓存与优化 (~3 小时) 本日目标: 理解 <KeepAlive> 的作用,学会如何使用它来缓存组件实例,从而优化应用性能和用户体验。所需资源: Vue 3 官方文档 (<KeepAlive>): https://cn.vuejs.org/guide/built-ins/…...
微软的 Windows Linux 子系统现已开源
微软宣布其 Windows Linux 子系统 (WSL) 开源,开放代码供社区成员贡献。自近九年前推出适用于 Windows 10 的 WSL 以来,微软多年来一直致力于开源这项在 Windows 中启用 Linux 环境的功能。 Windows 首席执行官 Pavan Davuluri 表示:“这是开…...
Axure中使用动态面板实现图标拖动交换位置
要在Axure中实现图标拖动交换位置的功能,可以通过动态面板结合交互事件来实现。 实现步骤 准备图标元素 将每个图标转换为动态面板(方便拖动和交互)。 设置拖动交互 选中图标动态面板 → 添加“拖动时”交互 → 选择“移动”当前动态面板&am…...
深入浅出:Spring Cloud Gateway 扩展点实践指南
文章目录 前言一、为什么需要扩展 Spring Cloud Gateway?二、Spring Cloud Gateway 核心扩展点三、扩展点实战:代码与配置详解3.1 全局过滤器(GlobalFilter)3.2 路由过滤器(GatewayFilter)2.3 自定义路由断…...
SCAU18923--二叉树的直径
18923 二叉树的直径 时间限制:1000MS 代码长度限制:10KB 提交次数:0 通过次数:0 题型: 编程题 语言: G;GCC Description 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点…...
理解 RESTful 风格:现代 Web 服务的基石
在当今的互联网时代,Web 服务成为了连接各种应用和系统的关键。而 RESTful 风格,作为一种广泛采用的架构风格,为设计和实现 Web 服务提供了一套简洁而强大的指导原则。本文将深入探讨 RESTful 风格的核心概念、优势以及如何在实际项目中应用它…...
大模型(3)——RAG(Retrieval-Augmented Generation,检索增强生成)
文章目录 1. 核心组成2. 工作流程3. 训练方式4. 优势与局限5. 应用场景6. 典型模型变体总结 RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合了信息检索与文本生成的技术,旨在通过引入外部知识库提升生成内容的准确性…...
电子科技大学软件工程实践期末
Java基础 面向对象 Java高级编程 2023: 软件工程基础 ch1软件工程概述 软件的概念和特点 软件危机的概念以及产生的原因 软件工程的定义 三要素 应用软件工程的原因 三要素:工具,方法,过程 ch2 软件过程 软件生命周期 软件过程…...
线上jvm假死问题排查
1.线上告警接口超时 看接口是用户服务,查看nacos服务实例,发现有一个节点已经下线了 3.找到对应节点所在服务器,jps -l 命令发现用户服务还在,初步判断是假死 4.使用 jstat -gc 进程id 1000 每秒打印gc情况,发现频繁…...
Redis中SETNX、Lua 脚本和 Redis事务的对比
在 Redis 中,SETNX、Lua 脚本 和 Redis 事务 都可以用于实现原子性操作,但它们的适用场景和能力范围不同。以下是详细对比和原因分析: 1. SETNX 的原子性与局限性 (1) 原子性保证 SETNX(SET if Not eXists) 是 Redis…...
Nginx配置记录访问信息
文章目录 方法一:使用Nginx原生配置记录访问信息方法二:使用Nginx_headers_more模块记录更加详细的信息 Nginx被广泛应用于各种场景如:Web服务器、反向代理服务器、负载均衡器、Web应用防火墙(WAF)等 在实际的产品开发中,无论是功…...
基于机载激光雷达数据的森林生物量估测:AI驱动的遥感革新
一、技术背景与意义 森林生物量是生态系统碳循环和碳汇估算的核心参数。传统遥感方法(如光学影像)在三维结构解析上存在局限,而机载激光雷达(LiDAR)凭借高精度点云数据,能够捕捉森林的垂直结构信息。结合人…...
Redis中的事务和原子性
在 Redis 中,事务 和 原子性 是两个关键概念,用于保证多个操作的一致性和可靠性。以下是 Redisson 和 Spring Data Redis 在处理原子性操作时的区别与对比: 1. Redis 的原子性机制 Redis 本身通过以下方式保证原子性: 单线程模型…...
SSL证书:谷歌算法排名的安全基石与信任杠杆
一、技术演进:从安全信号到算法基石 谷歌对SSL证书的重视始于2014年,当时HTTPS首次被纳入排名算法信号。经过十年迭代,SSL证书已从“加分项”升级为“基础门槛”。2025年算法更新中,其权重占比达2%,与页面加载速度、移…...
XXX企业云桌面系统建设技术方案书——基于超融合架构的安全高效云办公平台设计与实施
目录 1. 项目背景与目标1.1 背景分析1.2 建设目标2. 需求分析2.1 功能需求用户规模与场景终端兼容性2.2 非功能需求3. 系统架构设计3.1 总体架构图流程图说明3.2 技术选型对比3.3 网络设计带宽规划公式4. 详细实施方案4.1 分阶段部署计划4.2 桌面模板配置4.3 测试方案性能测试工…...
【GESP真题解析】第 18 集 GESP 一级 2024 年 12 月编程题 1:温度转换
大家好,我是莫小特。 这篇文章给大家分享 GESP 一级 2024 年 12 月编程题第 1 题:温度转换。 题目链接 洛谷链接:B4062 温度转换 一、完成输入 根据题意,输入只有一行,为实数,数据范围: 0 &l…...
鸿蒙开发进阶:深入解析ArkTS语言特性与高性能编程实践
一、前言 在鸿蒙生态蓬勃发展的当下,开发者对于高效、优质的应用开发语言需求愈发迫切。ArkTS 作为鸿蒙应用开发的核心语言,在继承 TypeScript 优势的基础上,进行了诸多优化与扩展,为开发者带来了全新的编程体验。本文将深入剖析…...
现代计算机图形学Games101入门笔记(十七)
双向路径追踪 外观建模 散射介质 人的头发不能用在动画的毛发上。 动物的髓质Medulla特别大 双层圆柱模型应用 BSSRDF是BRDF的延伸。 天鹅绒用BRDF不合理,转成散射介质。 法线分布 光追很难处理微表面模型 光在微型细节上,光是一个波,会发生衍…...
工单派单应用:5 大核心功能提升协作效率
一、工单管理:全流程一目了然 快速创建:录入任务内容、优先级,从源头明确目标 状态分类:待处理 / 进行中 / 已完成工单一目了然,个人进度随时掌控 灵活分配:公海池抢单机制,成员按能力自主接…...
maven 多个模块之间互相引入加载配置的偶遇问题
因为子项目添加了:<!-- aliyun sms SDK --> <dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.6.3</version> </dependency>导致原本运行良好的构建模块,…...
【蓝桥杯嵌入式】【模块】五、ADC相关配置及代码模板
1. 前言 最近在准备16届的蓝桥杯嵌入式赛道的国赛,打算出一个系列的博客,记录STM32G431RBT6这块比赛用板上所有模块可能涉及到的所有考点,如果有错误或者遗漏欢迎各位大佬斧正。 本系列博客会分为以下两大类: 1.1. 单独模块的讲…...
DP2 跳台阶【牛客网】
文章目录 零、原题链接一、题目描述二、测试用例三、解题思路四、参考代码 零、原题链接 DP2 跳台阶 一、题目描述 二、测试用例 三、解题思路 基本思路: 动态规划题目的难点基本在于构造状态转移方程,对应这题,我们可以发现每次跳跃我…...
KC 喝咖啡/书的复制/奶牛晒衣服/ 切绳子
二分的解题思路: 常解决最小值最大化和最大值最小化问题 步骤解析 确定答案范围 设定初始左边界 left 和右边界 right,确保解在此区间内。例如: 求最小最大值时,left 可取单个元素的最大值,right 取所有元素总和。 …...
Jedis快速入门【springboot】
引入依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>6.0.0</version> </dependency> 创立jedis对象,建立连接 private Jedis jedis; BeforeEach void setUp(){//1 …...
SpringBoot 商城系统高并发引起的库存超卖库存问题 乐观锁 悲观锁 抢购 商品秒杀 高并发
介绍 在高并发场景下,特别是商品秒杀、抢购等情况下,库存超卖问题是一个常见且棘手的问题。为了解决这个问题,Spring Boot 常使用乐观锁和悲观锁来保证数据的正确性和一致性。 悲观锁 悲观锁假设在多线程或多进程环境中,资源会被…...
[python] 轻量级定时任务调度库schedule使用指北
schedule是一款专为简化定时任务调度而设计的Python库,它通过直观的语法降低了周期性任务的实现门槛。作为进程内调度器,它无需额外守护进程,轻量且无外部依赖,适合快速搭建自动化任务。不过,该库在功能完整性上有所取…...
MySQL:to many connections连接数过多
当你遇到 MySQL: Too many connections 错误时,意味着当前连接数已达到 MySQL 配置的最大限制。这通常是由于并发连接过多或连接未正确关闭导致的。 一、查看当前连接数 查看 MySQL 当前允许的最大连接数 SHOW VARIABLES LIKE max_connections;查看当前使用的最大…...
uthash是一个非常轻量级的库
如大家所知,uthash是一个非常轻量级的库。该库的使用非常简单,无需格外的静态库或动态库,仅需导入目标的头文件即可。 这种配置方式虽然简单,但是使用操作却需要用到大量的宏函数。在使用宏函数时不像使用普通函数一样自由和遍历…...
大模型的开发应用(三):基于LlaMAFactory的LoRA微调(上)
基于LlaMAFactory的LoRA微调(上) 0 前言1 LoRA微调1 LoRA微调的原理1.2 通过peft库为指定模块添加旁支1.3 lora前后结构输出结果对比1.4 使用PyTorch复现 LoRA.Linear1.5 使用peft进行LoRA微调案例 2 LLaMA-Factory2.1 LLaMA-Factory简介2.2 LLaMA-Facto…...
跨域_Cross-origin resource sharing
同源是指"协议域名端口"三者相同,即便两个不同的域名指向同一个ip,也非同源 1.什么是CORS? CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器ÿ…...
奥威BI:打破AI数据分析伪场景,赋能企业真实决策价值
在当今企业数字化转型的浪潮中,AI数据分析产品如雨后春笋般涌现,但许多看似创新的功能设计实则难以落地,沦为“伪需求场景”。这些伪场景不仅浪费企业资源,还可能误导决策,阻碍企业数字化转型进程。在此背景下…...
LLaMA-Factory全解析:大模型微调的开源利器与实战指
技术演进背景与核心价值架构设计与关键技术解析环境搭建与工具链配置全流程微调实战指南企业级应用与高级功能性能优化与安全部署未来发展趋势展望1. 技术演进背景与核心价值 1.1 大模型微调的技术痛点 当前开源大模型(如LLaMA、Qwen、Baichuan等)在通用领域表现优异,但垂…...
python-数据可视化(大数据、数据分析、可视化图像、HTML页面)
通过 Python 读取 XLS 、CSV文件中的数据,对数据进行处理,然后生成包含柱状图、扇形图和折线图的 HTML 报告。这个方案使用了 pandas 处理数据,matplotlib 生成图表,并将图表嵌入到 HTML 页面中。 1.XSL文件生成可视化图像、生成h…...