项目需求文档
项目名称: 智慧零售全渠道业务中台系统 V1.0
项目背景: 为解决公司线上商城、线下门店、第三方平台(如天猫、京东)等多渠道业务数据孤岛、库存不同步、会员体验割裂等问题,构建统一的中台系统,实现业务融合和数字化管理。
项目目标:
- 统一商品中心: 实现全渠道商品信息"一处录入,处处可见"。
- 统一库存中心: 实现线上线下库存打通,支持安全库存、库存预警和自动分配。
- 统一订单中心: 处理来自各渠道的订单,实现履约流程标准化。
- 统一会员中心: 建立全渠道会员体系,实现积分、等级、权益互通。
- 高可用与高性能: 系统核心接口响应时间 < 200ms,保证99.9%的可用性。
角色与用例
- 系统管理员: 管理用户、角色、权限、系统参数。
- 商品运营人员: 管理商品信息、品类、价格、上下架。
- 库存管理人员: 管理库存数量、查看库存流水、设置安全库存。
- 客服人员: 查询订单、处理售后申请。
- 消费者: (在商城/门店)浏览商品、下单、查询订单、管理会员信息。
功能需求详述 (核心模块)
商品中心 (Product Center)
- SPU/SKU管理: 支持标准商品单元和库存单元模型。
- 品类管理: 多级品类树状结构。
- 价格管理: 支持原价、促销价,并可扩展不同渠道价格。
- 商品上下架: 支持批量操作。
- 商品信息检索: 基于ES的复杂条件搜索。
库存中心 (Inventory Center)
- 库存查询: 实时查询SKU的可用库存。
- 库存操作: 支持入库、出库、盘点、调拨等操作,并记录流水。
- 库存锁定: 下单时锁定库存,支付成功后扣减库存,取消订单时释放库存。
- 安全库存预警: 当库存低于安全值时,自动发送告警消息。
订单中心 (Order Center)
- 订单创建: 接收来自各渠道的订单数据,进行基础校验。
- 订单流程: 状态机管理(待支付、已支付、配货中、已发货、已完成、已取消等)。
- 订单查询: 多条件组合查询订单详情。
- 订单履约: 与WMS(仓库管理系统)对接,触发发货。
会员中心 (Member Center)
- 会员信息: 管理会员基础信息、收货地址。
- 会员等级: 根据成长值自动升降级。
- 积分管理: 积分赚取和消耗规则,积分流水记录。
非功能要求
- 性能: 核心接口QPS >= 1000,支持弹性扩缩容。
- 安全性: 数据脱敏、SQL注入/XSS防护、API访问鉴权。
- 可扩展性: 微服务架构,业务模块可独立部署和扩展。
- 可靠性: 关键业务数据必须有备份和恢复方案,消息队列确保最终一致性。
项目原型
登录页: 用户名/密码登录,可选验证码。
管理后台主页: 左侧导航菜单(商品管理、库存管理、订单管理、会员管理、系统设置),顶部用户信息,中部数据仪表盘(今日订单数、库存预警数等)。
商品列表页:
- 搜索区: 按商品名称、ID、品类、状态筛选。
- 操作区: "新增商品"、"批量上架"、"导出"按钮。
- 表格: 展示商品ID、名称、主图、价格、库存、状态(上下架)、操作列(编辑、查看、删除)。
订单详情页:
- 订单概要: 订单号、金额、状态、用户信息、支付信息。
- 商品清单: 购买的商品列表、数量、价格。
- 操作日志: 订单状态变更的历史记录。
- 操作按钮: 根据当前状态显示"审核"、"发货"、"取消"等按钮。
工具: 原型设计可使用 Axure RP、Figma 或墨刀等工具产出高保真交互原型。
技术栈
类别 技术选型 说明
后端框架 Spring Boot 2.x, Spring Cloud Alibaba 微服务基础套件
服务注册/发现 Nacos 替代Eureka,功能更强大
配置中心 Nacos 统一管理微服务配置
API网关 Spring Cloud Gateway 路由、过滤、限流、鉴权
RPC框架 Apache Dubbo / Feign 服务间调用
数据持久化 MySQL 8.0 业务关系型数据存储
ORM框架 MyBatis-Plus 增强MyBatis,简化开发
缓存 Redis (Redisson) 缓存热点数据、分布式锁、会话管理
搜索引擎 Elasticsearch 7.x 商品、订单等数据的复杂搜索
消息队列 RabbitMQ / RocketMQ 业务解耦,最终一致性(如订单和库存)
分布式事务 Seata 处理跨服务的分布式事务问题
容器化 Docker 应用容器化
编排部署 Kubernetes (K8s) 容器编排、自动化部署、扩缩容
监控预警 Prometheus + Grafana 监控系统指标和业务指标
日志系统 ELK (Elasticsearch, Logstash, Kibana) 日志收集、分析和可视化
前端 Vue 3 + Element Plus 构建管理后台前端页面
CI/CD Jenkins / GitLab CI 自动化流水线
重点代码技术亮点
库存服务
java
// 零售库存服务远程调用接口
@FeignClient(value = "retail-inventory-service", path = "/api/inventory")
public interface InventoryFeignClient {
@PostMapping("/deduct")
Result<Boolean> deductStock(@RequestBody InventoryDeductRequest request);
}
// InventoryDeductRequest.java
@Data
public class InventoryDeductRequest implements Serializable {
private String skuCode;
private Integer quantity;
private String orderToken; // 用于幂等性控制
}
java
@Data
@TableName("t_inventory")
public class Inventory {
@TableId(type = IdType.INPUT) // SKU编码作为主键
private String skuCode;
private Integer totalQuantity;
private Integer availableQuantity;
private Integer lockedQuantity;
private Integer safetyStock;
@Version // 乐观锁版本号,防止超卖
private Integer version;
}
java
@Service
@Slf4j
public class InventoryServiceImpl implements InventoryService {
private final InventoryMapper inventoryMapper;
private final InventoryLogService inventoryLogService;
private final RedisTemplate<String, Object> redisTemplate;
@Override
@Transactional(rollbackFor = Exception.class) // 本地事务
public boolean deductStock(InventoryDeductRequest request) {
// 幂等性校验:通过Redis判断orderToken是否已处理过
String key = "inventory:deduct:token:" + request.getOrderToken();
Boolean isAbsent = redisTemplate.opsForValue().setIfAbsent(key, "PROCESSED", Duration.ofMinutes(30));
if (Boolean.FALSE.equals(isAbsent)) {
log.warn("重复的库存扣减请求,已忽略。orderToken: {}", request.getOrderToken());
return true; // 已经处理过了,返回成功
}
// 使用MyBatis-Plus的乐观锁更新
UpdateWrapper<Inventory> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("sku_code", request.getSkuCode())
.ge("available_quantity", request.getQuantity()) // 确保可用库存足够
.setSql("available_quantity = available_quantity - " + request.getQuantity())
.setSql("locked_quantity = locked_quantity + " + request.getQuantity());
// .eq("version", currentVersion) // 如果需要更严格的控制,可以加上版本号
int affectedRows = inventoryMapper.update(null, updateWrapper);
if (affectedRows == 0) {
log.error("库存扣减失败,可能库存不足或数据不存在。SKU: {}", request.getSkuCode());
throw new RuntimeException("库存扣减失败,SKU: " + request.getSkuCode());
}
// 记录库存操作流水,非常重要,用于对账和追溯
inventoryLogService.recordDeductLog(request.getSkuCode(), request.getQuantity(), request.getOrderToken());
log.info("库存扣减成功,SKU: {}, 数量: {}", request.getSkuCode(), request.getQuantity());
return true;
}
}
订单服务
- 操作订单的生成添加数据库
- 远程调用库存服务
- 异步调用消息中间件服务rabbitMQ
- 保证生成订单的唯一性,幂等
- 分布式事务Seata AT模式 保证 订单创建 和 库存锁定 的最终一致性
java
@Service
@Slf4j
@RequiredArgsConstructor // Lombok注解,生成构造函数注入
public class OrderServiceImpl implements OrderService {
private final OrderMapper orderMapper;
private final InventoryFeignClient inventoryFeignClient;
private final RabbitTemplate rabbitTemplate;
private final SnowflakeIdGenerator idGenerator; // 自定义雪花ID生成器
@Override
@GlobalTransactional(name = "retail-create-order", rollbackFor = Exception.class) // Seata全局事务注解
public String createOrder(OrderCreateRequest request) {
log.info("开始创建订单,用户: {}", request.getUserId());
// 1. 生成唯一订单号和业务token,用于幂等
String orderId = generateOrderId();
String orderToken = generateOrderToken();
// 2. 循环调用库存服务,锁定每个SKU的库存
for (OrderCreateRequest.OrderItemRequest item : request.getOrderItems()) {
InventoryDeductRequest deductRequest = new InventoryDeductRequest();
deductRequest.setSkuCode(item.getSkuCode());
deductRequest.setQuantity(item.getQuantity());
deductRequest.setOrderToken(orderToken); // 传递token,库存服务做幂等校验
Result<Boolean> deductResult = inventoryFeignClient.deductStock(deductRequest);
if (!deductResult.isSuccess() || !deductResult.getData()) {
log.error("锁定库存失败,SKU: {}, 原因: {}", item.getSkuCode(), deductResult.getMessage());
throw new RuntimeException("库存锁定失败: " + item.getSkuCode());
}
log.info("SKU: {} 库存锁定成功", item.getSkuCode());
}
// 3. 计算总价等业务逻辑 (简化)
BigDecimal totalAmount = calculateTotalAmount(request.getOrderItems());
// 4. 落库订单主数据
Order order = new Order();
order.setOrderId(orderId);
order.setUserId(request.getUserId());
order.setChannel(request.getChannel());
order.setTotalAmount(totalAmount);
order.setStatus("PENDING_PAYMENT");
orderMapper.insert(order);
log.info("订单数据落库成功,订单号: {}", orderId);
// 5. 发送订单创建成功消息到MQ,通知其他系统(如优惠券、积分、营销)
// 即使后续系统重启,此消息也可能被重复消费,消费者需要做幂等处理
try {
rabbitTemplate.convertAndSend("order.exchange", "order.created", orderId);
log.info("订单创建消息发送成功: {}", orderId);
} catch (AmqpException e) {
log.error("订单创建消息发送失败: {}", orderId, e);
// 可根据业务决定是重试还是记录日志人工介入,通常不影响主流程
}
return orderId;
}
private String generateOrderId() {
return "ORDER" + idGenerator.nextIdStr();
}
// ... 其他方法省略
}
java
@Data
public class OrderCreateRequest {
@NotNull(message = "用户ID不能为空")
private Long userId;
@NotBlank(message = "渠道不能为空")
private String channel;
@Valid
@NotNull(message = "配送地址不能为空")
private DeliveryAddress deliveryAddress;
@NotEmpty(message = "订单项不能为空")
private List<OrderItemRequest> orderItems;
@Data
public static class DeliveryAddress {
private String receiver;
private String phone;
private String province;
private String city;
private String district;
private String detail;
}
@Data
public static class OrderItemRequest {
@NotBlank(message = "SKU编码不能为空")
private String skuCode;
@Min(value = 1, message = "数量必须大于0")
private Integer quantity;
@DecimalMin(value = "0.0", message = "价格不能为负")
private BigDecimal price;
}
}
@Data
@TableName("t_order")
public class Order {
@TableId(type = IdType.ASSIGN_ID) // 使用雪花算法生成ID
private Long id;
private String orderId; // 业务订单号,如 ORDER20231027100100001
private Long userId;
private BigDecimal totalAmount;
private String status; // PENDING_PAYMENT, PAID, DELIVERED, etc.
private String channel; // MINI_PROGRAM, APP, OFFLINE_STORE
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
乐观锁防止超卖
①、库存实体类添加version字段
java
// Inventory.java
@Data
@TableName("t_inventory")
public class Inventory {
@TableId(type = IdType.INPUT)
private String skuCode;
private Integer totalQuantity;
private Integer availableQuantity;
private Integer lockedQuantity;
private Integer safetyStock;
@Version // MyBatis-Plus 乐观锁注解,必备!
private Integer version; // 版本号字段
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
②、配置MyBatis-Plus乐观锁插件
java
// MyBatisPlusConfig.java
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
③、实现扣减库存服务(乐观锁版)
不再使用UpdateWrapper进行数学计算,而是先查询,再带着版本号更新
java
// InventoryServiceImpl.java
@Service
@Slf4j
@RequiredArgsConstructor
public class InventoryServiceImpl implements InventoryService {
private final InventoryMapper inventoryMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(InventoryDeductRequest request) {
// 1. 根据skuCode查询当前库存信息和版本号
Inventory inventory = inventoryMapper.selectById(request.getSkuCode());
if (inventory == null) {
throw new RuntimeException("库存不存在, SKU: " + request.getSkuCode());
}
if (inventory.getAvailableQuantity() < request.getQuantity()) {
throw new RuntimeException("库存不足, SKU: " + request.getSkuCode());
}
// 2. 计算新的值(内存中计算)
int newAvailableQuantity = inventory.getAvailableQuantity() - request.getQuantity();
int newLockedQuantity = inventory.getLockedQuantity() + request.getQuantity();
// 3. 构建Entity进行更新,MyBatis-Plus乐观锁插件会自动帮我们在WHERE条件中加上 version = #{oldVersion}
Inventory updateEntity = new Inventory();
updateEntity.setSkuCode(request.getSkuCode());
updateEntity.setAvailableQuantity(newAvailableQuantity);
updateEntity.setLockedQuantity(newLockedQuantity);
updateEntity.setVersion(inventory.getVersion()); // 设置旧的版本号,这是乐观锁的核心
// 4. 执行更新
int affectedRows = inventoryMapper.updateById(updateEntity);
// 5. 判断更新结果
if (affectedRows == 0) {
// 更新失败,说明数据已被其他事务修改,版本号对不上
log.warn("库存扣减乐观锁冲突,操作失败,SKU: {}。可能原因:其他请求同时修改了库存。", request.getSkuCode());
throw new RuntimeException("库存扣减失败,请重试");
}
// 6. 更新成功,记录流水等后续操作...
log.info("库存扣减成功,SKU: {}, 数量: {}", request.getSkuCode(), request.getQuantity());
return true;
}
}
乐观锁和分布式锁进行结合
场景:防止重复请求穿透到数据库
如果一个用户连续疯狂点击下单,大量请求瞬间到达订单服务,这些请求都会去调用库存服务的deductStock方法。即使有乐观锁,大量的请求也会直接打到数据库上进行UPDATE操作,虽然不会超卖,但会给数据库造成巨大压力。
解决方案:分布式锁进行"削峰"和"幂等"
在库存服务deductStock方法的最外层,针对同一个SKU的扣减操作加一个细粒度的分布式锁。
java
@Override
public boolean deductStock(InventoryDeductRequest request) {
// 分布式锁的Key,精确到SKU和业务token,保证粒度最细
String lockKey = "lock:inventory:deduct:" + request.getSkuCode() + ":" + request.getOrderToken();
// 使用Redisson获取分布式锁
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,waitTime=0(拿不到锁立即返回),leaseTime=3s(自动释放,防止死锁)
boolean isLocked = lock.tryLock(0, 3, TimeUnit.SECONDS);
if (!isLocked) {
log.warn("获取分布式锁失败,可能是重复请求,SKU: {}, token: {}", request.getSkuCode(), request.getOrderToken());
throw new RuntimeException("系统繁忙,请勿重复操作");
}
// 真正执行扣减库存的业务逻辑(内部是乐观锁)
return doDeductStock(request);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("系统中断异常", e);
} finally {
// 无论如何,最终要释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 私有方法,包含之前的乐观锁扣减逻辑
private boolean doDeductStock(InventoryDeductRequest request) {
// ... 这里就是上面第三部分的乐观锁代码 ...
}
分布式锁(外层):
- 作用:将对同一个SKU的并发请求串行化,一瞬间只有一个请求能进入核心业务逻辑。
- 目的:保护数据库,防止瞬间高并发压垮DB。同时利用orderToken实现了接口层的幂等性。
乐观锁(内层):
- 作用:保证在数据库层面更新的绝对安全,防止任何情况下的超卖。
- 目的:作为最后的、最可靠的防线。
认证授权 (Spring Security + JWT + OAuth2 资源服务器模式)
一、依赖
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.31</version>
</dependency>
</dependencies>
二、安全配置
java
@Configuration
@EnableWebSecurity //web安全
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
//特定路径公开,其他路径需要验证
.antMatchers("/auth/login", "/auth/token/refresh").permitAll()
.anyRequest().authenticated()
)
//简单的JWT配置
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
//明确配置为无状态
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
//明确禁用CSRF
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
//内置HMAC密钥解码器
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withSecretKey(
Keys.hmacShaKeyFor("my-secret-key-which-must-be-at-least-32-characters-long".getBytes())
).build();
}
}
java
@Configuration
@EnableMethodSecurity(prePostEnabled = true) // 启用方法级安全注解
public class ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
//所有请求路径都需要认证
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
//OAuth2资源服务器 + JWT + 自定义转换器
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(new CustomJwtGrantedAuthoritiesConverter());
return converter;
}
}
java
public class CustomJwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
String scope = jwt.getClaim("scope");
return Arrays.stream(scope.split(" "))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}

Spring Security不允许同时存在多个SecurityFilterChain Bean,除非明确指定执行顺序或使用不同的请求匹配条件。
- 多个SecurityFilterChain Bean:两个配置类都定义了filterChain方法
- 重复的安全配置:都配置了oauth2ResourceServer
- 竞争性配置:Spring不知道应该使用哪个配置
在冲突下,如何共存?
方案一:使用@Order指定优先级
java
@Configuration
@Order(1) // 高优先级
public class ResourceServerConfig { ... }
@Configuration
@Order(2) // 低优先级
public class SecurityConfig { ... }
方案二:不同的请求匹配
java
// 配置1:处理/api/**请求
@Bean
@Order(1)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**")...;
return http.build();
}
// 配置2:处理其他请求
@Bean
@Order(2)
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/**")...;
return http.build();
}
方案3:条件化Bean注册
java
@Configuration
public class CompositeSecurityConfig {
@Bean
@ConditionalOnExpression("'${security.mode}' == 'oauth2'")
public SecurityFilterChain oauth2FilterChain(HttpSecurity http) throws Exception {
// OAuth2配置
}
@Bean
@ConditionalOnExpression("'${security.mode}' == 'jwt'")
public SecurityFilterChain jwtFilterChain(HttpSecurity http) throws Exception {
// JWT配置
}
}
三、登录接口
java
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final JwtEncoder jwtEncoder;
private final UserDetailsService userDetailsService;
@PostMapping("/login")
public Result<LoginResponse> login(@RequestBody @Valid LoginRequest request) {
// 1. 验证用户名密码 (实际应该查询数据库)
UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
if (!passwordEncoder().matches(request.getPassword(), userDetails.getPassword())) {
throw new BadCredentialsException("用户名或密码错误");
}
// 2. 生成JWT
Instant now = Instant.now();
long expiry = 3600L; // 1小时过期
String scope = String.join(" ", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("retail-auth-service")
.issuedAt(now)
.expiresAt(now.plusSeconds(expiry))
.subject(userDetails.getUsername())
.claim("scope", scope)
.claim("userId", "10001") // 自定义claims
.build();
String token = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
return Result.success(new LoginResponse(token, "bearer", expiry));
}
@Data
public static class LoginRequest {
private String username;
private String password;
}
@Data
@AllArgsConstructor
public static class LoginResponse {
private String accessToken;
private String tokenType;
private Long expiresIn;
}
}
方法级权限控制
java
@RestController
@RequestMapping("/api/order")
@PreAuthorize("hasAuthority('ORDER_MANAGE')") // 类级别权限控制
public class OrderController {
@PostMapping
@PreAuthorize("hasAuthority('ORDER_CREATE')") // 方法级别权限控制
public Result<String> createOrder(@Valid @RequestBody OrderCreateRequest request) {
// 获取当前用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
// ...
}
@GetMapping("/{orderId}")
@PreAuthorize("hasAuthority('ORDER_READ') or @permissionService.canViewOrder(#orderId, authentication)")
public Result<Order> getOrder(@PathVariable String orderId) {
// 自定义权限校验
return Result.success(orderService.getOrder(orderId));
}
}
熔断降级(Sentinel)
一、添加依赖
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
二、 Sentinel配置 (application.yml)
yml
spring:
cloud:
sentinel:
enabled: true
eager: true
transport:
dashboard: localhost:8080 # Sentinel控制台地址
datasource:
ds1:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-sentinel
groupId: SENTINEL_GROUP
rule-type: flow
ds2:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-sentinel-degrade
groupId: SENTINEL_GROUP
rule-type: degrade
三、Feign整合Sentinel
java
@FeignClient(
value = "retail-inventory-service",
path = "/api/inventory",
fallback = InventoryFeignClientFallback.class // 降级类
)
public interface InventoryFeignClient {
@PostMapping("/deduct")
Result<Boolean> deductStock(@RequestBody InventoryDeductRequest request);
}
// 降级实现
@Component
@Slf4j
public class InventoryFeignClientFallback implements InventoryFeignClient {
@Override
public Result<Boolean> deductStock(InventoryDeductRequest request) {
log.error("库存服务调用失败,触发降级,SKU: {}", request.getSkuCode());
// 1. 可以返回一个默认值
// return Result.fail(500, "库存服务暂时不可用");
// 2. 或者抛出异常,让上层处理
throw new RuntimeException("库存服务调用失败,触发降级");
}
}
自定义全局异常整合Sentinel
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BlockException.class)
public Result<String> handleBlockException(BlockException e) {
return Result.fail(429, "请求过于频繁,请稍后重试");
}
@ExceptionHandler(SentinelRpcException.class)
public Result<String> handleSentinelRpcException(SentinelRpcException e) {
return Result.fail(503, "下游服务暂时不可用");
}
}
四、使用@SentinelResource进行方法级保护
java
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
@SentinelResource(
value = "createOrder",
blockHandler = "createOrderBlockHandler", // 流控降级处理
fallback = "createOrderFallback", // 业务异常处理
exceptionsToIgnore = {IllegalArgumentException.class} // 忽略的异常
)
@GlobalTransactional
public String createOrder(OrderCreateRequest request) {
// 业务逻辑
}
// 流控降级处理方法
public String createOrderBlockHandler(OrderCreateRequest request, BlockException ex) {
log.warn("创建订单被限流,请求被拒绝", ex);
throw new RuntimeException("系统繁忙,请稍后重试");
}
// 业务降级处理方法
public String createOrderFallback(OrderCreateRequest request, Throwable ex) {
log.error("创建订单发生异常,触发降级", ex);
throw new RuntimeException("订单创建失败,请稍后重试");
}
}