智慧零售全渠道业务中台系统

项目需求文档

项目名称: 智慧零售全渠道业务中台系统 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("订单创建失败,请稍后重试");
    }
}
相关推荐
桐果云3 小时前
解锁桐果云零代码数据平台能力矩阵——赋能零售行业数字化转型新动能
大数据·人工智能·矩阵·数据挖掘·数据分析·零售
港股研究社3 小时前
涨了一倍多的顺丰同城,还能继续做大即时零售基建的蛋糕吗?
零售
小wanga6 小时前
C++知识
java·开发语言·c++
我是渣哥6 小时前
Java String vs StringBuilder vs StringBuffer:一个性能优化的探险故事
java·开发语言·jvm·后端·算法·职场和发展·性能优化
工一木子6 小时前
深入Java并发:锁机制原理剖析与性能优化实战
java·性能优化·并发·
你我约定有三6 小时前
java--写在 try 中的创建连接
java·开发语言
ERP老兵-冷溪虎山6 小时前
Python/JS/Go/Java同步学习(第三篇)四语言“切片“对照表: 财务“小南“纸切片术切凭证到崩溃(附源码/截图/参数表/避坑指南/老板沉默术)
java·javascript·python·golang·中医编程·四语言同步学习·职场生存指南
科技树支点6 小时前
无GC的Java创新设计思路:作用域引用式自动内存管理
java·python·go·web·编程语言·编译器