☰
拆解若依SpringCloud骨架:Nacos、Sentinel与Seata实战要点
2026/10/18 18:34:09 网站建设 项目流程

简介:这是一套基于若依框架的微服务前后端分离项目资源,后端采用Spring Boot、Spring Cloud & Alibaba技术体系,Nacos承担注册与配置中心,Redis用于权限认证,Sentinel实现限流熔断,Seata处理分布式事务,前端对应Vue3、Element Plus与Vite版本,完整覆盖服务治理、配置管理、流量控制与分布式事务等核心场景。资源包为RAR格式,约132.24MB,共677个文件,核心文件类型包括261个Java源码、89个Vue组件、88个SVG图标、75个JavaScript脚本,并配套46个XML配置、7个SQL脚本、11个Dockerfile以及12个Bat批处理文件,覆盖后端业务、前端页面、数据库初始化、容器化部署和一键启动等完整环节。目录中设有网关、认证、系统、文件、代码生成、定时任务、监控等多个模块的启动与打包脚本,方便在本地快速拉起整套分布式环境,并可直接观察各微服务组件的协作方式。已有3578人学习/下载,资源既适合想深入理解微服务治理及分布式事务落地的Java开发者,也可作为企业级中后台项目二次开发的基础模板。

1. 微服务骨架的后悔药:若依SpringCloud为什么值得拆开看

如果你们团队正打算从单体Spring Boot往SpringCloud迁移,这套若依SpringCloud版本值得先下载跑一遍再动手。它把Nacos注册与配置中心、Sentinel限流熔断、Seata分布式事务、Vue3前端全部揉进了一个能编译、能启动、能联调的后台管理系统骨架里。过去拆微服务最头疼的三件事——服务间怎么发现、流量怎么保护、跨服务数据怎么保持一致——在这个项目里都有现成的落地方案。适合两类人:一类是想快速搭运营后台的Java全栈,另一类是正准备从单体拆微服务、想找一个“参考架构”的团队技术负责人。这篇笔记按“架构长什么样、Nacos怎么落地、Sentinel与Seata怎么协作、踩过哪些坑、怎么加自己的模块”这条线来拆。

2. 整体架构拆解:一套微服务后台从网关到数据库的完整链路

2.1 服务划分与模块职责

若依SpringCloud不是把原来的单体Controller拆成几个包糊弄出来,而是按业务域拆成了独立进程。网关层是ruoyi-gateway,负责统一入口、路由转发、跨域处理;认证服务ruoyi-auth负责登录鉴权,issue的Token基于OAuth2改造而来,接管了系统内的Token签发与刷新;核心业务域在ruoyi-modules下面,包括系统管理ruoyi-system、定时任务ruoyi-job、代码生成ruoyi-gen、文件服务ruoyi-file。这四个业务服务都是可独立打包、独立启动的Spring Boot应用。

公共能力沉淀在ruoyi-common层,这里面有几个模块值得重点看:ruoyi-common-security封装了Security上下文传递,ruoyi-common-log是操作日志切面,ruoyi-common-sentinel与ruoyi-common-seata分别是Sentinel与Seata的集成支撑。从开发角度讲,通用逻辑沉淀到common是微服务拆分后的必然选择,否则每个业务服务各写一套鉴权和日志,后期维护成本会迅速失控。

2.2 从请求进入到返回的完整链路

一次前端请求的完整路径是:Vue3页面通过/prod-api代理到网关,网关根据路由表把请求转发给对应服务。我先贴一段网关的核心配置:

spring: application: name: ruoyi-gateway cloud: gateway: routes: - id: ruoyi-auth uri: lb://ruoyi-auth predicates: - Path=/auth/** filters: - StripPrefix=1 - id: ruoyi-system uri: lb://ruoyi-system predicates: - Path=/system/** filters: - StripPrefix=1

lb://是SpringCloud Gateway内置的负载均衡前缀,它会让网关通过Nacos拿到ruoyi-auth服务的实例列表,再按负载均衡策略选一个实例转发。StripPrefix=1表示转发时去掉第一段路径,比如/auth/login实际到达后端服务的路径是/login,这能让后端服务不用感知前端的多层路径前缀。请求进入系统服务后,先经过Sentinel的流量防护,再走Security鉴权,最终落到MyBatis的Mapper层访问数据库;一旦某个操作涉及跨服务写数据,Seata的全局事务会接管整个回滚链路。

2.3 前端Vue3与后端服务的部署边界

前端如果走Dev模式,Vite的代理配置直接指向网关地址,开发机上不用装Nginx也能联调;生产环境则建议把前端打包后的静态文件交给Nginx托管,location /prod-api/反代到网关端口。有一点要注意:网关本身已经做了跨域配置,所以前端代理与网关跨域不用同时开,否则预检请求会被拦截两次,出现“明明配置了Allow-Origin还是报跨域”的玄学问题。

3. Nacos落地细节:注册中心与配置中心双开,服务之间怎么互相发现

3.1 注册中心:服务名、命名空间与分组

所有服务启动后会把自身注册到Nacos,注册信息包含服务名、IP、端口、健康检查探针。这套框架里服务名统一用ruoyi-前缀,方便在Nacos控制台里按前缀过滤。命名空间建议按环境划分,比如dev、test、prod各建一个namespace,这样多个环境的服务列表互不可见,避免测试服务被生产网关误路由。注意Nacos的namespace是逻辑隔离不是物理隔离,同一个Nacos集群能承载多个环境的注册信息,所以小型团队不必为每个环境单独部署一套Nacos。

3.2 配置中心:共享配置、扩展配置与服务自身配置

Nacos同时承担配置中心角色,这是若依版本比单体版最大的改进。过去单体项目把application.yml放在Jar包里,改配置要重新打包;现在配置文件全部挪到Nacos,改完配置点发布,服务端监听配置变更自动刷新。基础配置如下:

spring: cloud: nacos: config: server-addr: 127.0.0.1:8848 namespace: 8f2d16c2-0b1c-4d4b-8f2d-5c7a3f123456 group: DEFAULT_GROUP file-extension: yml shared-configs: ->@SentinelResource(value = "selectUserList", blockHandler = "handleBlock") public List<SysUser> selectUserList(SysUser user) { return userMapper.selectUserList(user); } public List<SysUser> handleBlock(SysUser user, BlockException ex) { // 限流后的降级返回,按业务决定是给空列表还是抛业务异常 return Collections.emptyList(); }

value是资源名,在Sentinel控制台能看到这个资源的实时QPS与拒绝数量;blockHandler是限流后的兜底方法,方法签名必须与原始方法一致,外加一个BlockException参数。有一个新手容易踩的点:@SentinelResource只对方法调用内的代码生效,如果方法内部调用了其他类的方法,限流粒度还是外层方法;其次Sentinel规则默认存在内存里,重启就没了。生产环境我的习惯是把规则推送到Nacos持久化,通过DataSource扩展实现规则自动加载,避免每次上线后人工重新配一遍规则。

4.2 Seata分布式事务:AT模式的原理与落地

Seata在这套框架里用的是AT模式,核心思路是对业务无侵入:业务方只需要在入口方法加@GlobalTransactional,Seata自动拦截SQL,记录数据变更前镜像与后镜像,全局回滚时根据镜像生成反向SQL。先看业务入口的正确姿势:

@GlobalTransactional(rollbackFor = Exception.class) public void createOrder(Order order) { orderMapper.insert(order); accountService.decrease(order.getUserId(), order.getAmount()); }

rollbackFor指定回滚异常类型,这里必须写Exception.class,否则运行时异常不会触发全局回滚。注意accountService.decrease是远程调用,Seata会把XID通过Feign请求头传递到下游服务,下游服务的事务分支自动加入同一个全局事务。这行的关键前提是:下游服务的数据源必须被Seata代理,否则到下游就断链了。

4.3 数据源代理与undo_log表

Seata AT模式要求业务数据库里必须存在undo_log表,否则回滚时写不入日志。DDL如下:

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, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

xid是全局事务ID,branch_id是分支事务ID,rollback_info存的是before/after镜像,Seata靠这份镜像在回滚时恢复原数据。每个参与全局事务的业务库都必须建这张表,漏一个库就出现“部分服务回滚成功,部分回滚失败”的半成功状态。

数据源代理是另一个关键配置:

@Bean @ConfigurationProperties(prefix = "spring.datasource") public DruidDataSource druidDataSource() { return new DruidDataSource(); } @Bean("dataSource") public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource) { return new DataSourceProxy(druidDataSource); }

DataSourceProxy是Seata对数据源的代理封装,SQL先经过它记录镜像再执行到真实数据源。注意这个Bean名字必须是dataSource,因为MyBatis自动装配时会按类型查找,如果项目里原本有自定义数据源Bean,要确认代理链没有断。

5. 避坑与排查:从启动报错到数据不一致的实战记录

5.1 服务启动时Nacos连接失败

现象:启动gateway或system服务时控制台持续输出连接127.0.0.1:8848超时,服务无法注册成功;有时本地Nacos已经打开,但服务就是连不上。

原因:SpringBoot 2.4以上版本默认不再从bootstrap.yml加载Nacos配置,必须引入spring-cloud-starter-bootstrap依赖,否则Nacos的地址配置根本没被读到。

解决:在ruoyi-common的依赖管理里显式添加spring-cloud-starter-bootstrap依赖,或者把Nacos配置挪到spring.config.import语法下加载。检查Nacos服务端是否真实启动,用浏览器打开127.0.0.1:8848/nacos确认控制台可访问。

5.2 Sentinel规则重启后全部丢失

现象:在Sentinel控制台配置了流控规则,当时生效正常,服务重启后规则全部消失,接口恢复到不限流状态。

原因:默认规则存储在内存中,Sentinel控制台的操作只对当前运行实例生效,实例停止即丢失。

解决:引入nacos-datasource扩展,在Sentinel配置里声明数据源为Nacos,规则通过Nacos持久化,启动时自动加载。如果不想引入额外依赖,至少把核心资源的限流规则用代码预制到FlowRuleManager.loadRules(),保证每次服务启动时有兜底规则。

5.3 Seata全局回滚失败,出现数据半成功

现象:调用链下游抛异常后,上游数据已经回滚,但下游某个服务的数据仍然保留,出现订单已回滚而库存已扣的情况。

原因:大概率是下游服务的数据源没有走DataSourceProxy代理,或者undo_log表缺失,导致分支事务无法登记。

解决:逐库检查undo_log表是否存在,DDL见上;检查数据源配置类是否把@Bean("dataSource")返回类型改为DataSourceProxy;最后确认Seata Server与客户端使用相同的applicationId与tx-service-group。我遇到过一次奇葩情况,上游服务里配置了Seata但下游服务连依赖都没引入,Feign调用走了一圈完全没注册分支事务,排查时看Seata Server的日志会看到下游没有任何branch注册记录。

5.4 前端登录成功但跳转404

现象:Vue3前端输入账号密码登录成功,Token已经存进Cookie,但刷新页面后一直停留在404页面,后端日志没有相关请求记录。

原因:动态路由没有正确生成,前端根据后端返回的菜单权限调用getRouters接口,如果网关转发路径不对,路由数据拉取失败,前端拿不到可访问的菜单列表。

解决:打开浏览器Network面板看/system/menu/getRouters的请求状态;重点检查网关路由配置中StripPrefix是否与前端代理路径匹配;另一个常见问题是Cookie的SameSite属性影响了Token传递,开发环境把Token存到LocalStorage并设置拦截器统一携带可以绕过这个问题。

6. 二次开发落地:往这套骨架里加一个新的订单模块

新建业务模块不要从零开始Copy框架,正确的姿势是复制ruoyi-system改名。在ruoyi-modules下复制一份目录叫ruoyi-order,改四个地方:pom.xml的artifactId,启动类名与应用名,包名前缀com.ruoyi.order,Nacos配置文件ruoyi-order-dev.yml。启动类注意加上服务发现注解:

@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients(basePackages = "com.ruoyi.order.feign") public class RuoYiOrderApplication { public static void main(String[] args) { SpringApplication.run(RuoYiOrderApplication.class, args); } }

@EnableDiscoveryClient让服务注册到Nacos,basePackages指定Feign接口扫描路径,如果省略这个参数,Feign接口与启动类不在同包时会扫描不到。Nacos侧新增ruoyi-order-dev.yml配置文件,dataId规则是服务名-环境.yml,内容至少包含数据源与Seata事务分组。

网关路由追加一条指向新服务的规则,前端菜单通过若依自带的菜单管理界面在数据库中配置,不需要改前端源码。验证方式建议三步走:Nacos服务列表出现ruoyi-order实例;访问/system/order/list能返回数据;构造一个跨服务写入场景,比如创建订单同时扣减库存,手动在下游抛异常验证订单回滚。二次开发时尽量复用ruoyi-common-security的鉴权注解,不要在业务模块里另做一套登录拦截,否则会出现网关传了用户信息但业务模块不认账的尴尬。

这套框架我前后拆过不止一遍,每次拿到新版本都会先强制走一遍“改模块名、改配置、起服务、调通一个接口”的全流程。微服务这种东西,骨架搭对了后面全是体力活,骨架错了后面全是玄学。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询