首页 > 编程语言 >clean-java-project-structure-实现秒杀系统

clean-java-project-structure-实现秒杀系统

时间:2024-11-08 21:50:47浏览次数:3  
标签:java String private project clean new return public

clean-java-project-structure-意在clean&standard
断WAN手撕了一个平平无奇的秒杀系统,crud过载,赶紧多看看源码缓缓

秒杀系统实现 - 前言

在互联网高速发展的时代,电商平台的各种促销活动层出不穷,其中“秒杀”活动以其低价、限时、限量的特点吸引了大量用户,成为电商平台吸引流量、提升销量的有效手段。然而,秒杀系统由于其高并发、瞬间流量巨大的特点,对系统架构和技术选型提出了极高的要求。实现一个稳定、高效、公平的秒杀系统,并非易事。

秒杀系统的挑战:

  • 高并发: 秒杀活动开启瞬间,会涌入海量请求,系统需要具备处理高并发请求的能力。
  • 资源竞争: 多个用户同时抢购有限的商品,如何保证数据的一致性和避免超卖是关键问题。
  • 性能瓶颈: 数据库、缓存、网络带宽等都可能成为系统的性能瓶颈,需要进行合理的优化和设计。
  • 安全性: 秒杀系统容易成为攻击目标,需要防范恶意请求和刷单行为。
  • 公平性: 如何保证所有用户都有公平的参与机会,防止机器人或恶意脚本抢占资源,也是一个重要挑战。

构建秒杀系统的关键点:

  • 架构设计: 选择合适的架构模式,例如微服务架构、分布式架构,以应对高并发和海量数据。
  • 缓存策略: 利用缓存技术减轻数据库压力,提高系统响应速度。
  • 异步处理: 将一些耗时操作异步处理,例如订单生成、库存扣减等。
  • 限流降级: 通过限流和降级策略,保护系统在高并发情况下不崩溃。
  • 安全防护: 采用多种安全措施,例如验证码、风控系统等,防止恶意攻击和刷单行为。
  • 公平性保障: 设计合理的抢购策略和算法,确保用户公平参与秒杀活动。

本系列文章将深入探讨秒杀系统的实现,涵盖以下内容:

  • 需求分析与系统设计: 明确秒杀系统的功能需求、性能指标和技术选型。
  • 高并发架构设计: 介绍如何利用负载均衡、分布式缓存、消息队列等技术构建高并发架构。
  • 缓存策略与数据一致性: 探讨如何选择合适的缓存方案,并保证数据一致性。
  • 异步处理与性能优化: 介绍如何利用异步处理技术提高系统性能和响应速度。
  • 限流降级与熔断机制: 讨论如何设计限流、降级和熔断策略,保证系统稳定性。
  • 安全防护与反作弊: 介绍如何利用验证码、风控系统等技术防范恶意攻击和刷单行为。
  • 性能测试与调优: 通过性能测试发现系统瓶颈,并进行针对性的优化。

目标:

通过本系列文章,希望能够帮助读者理解秒杀系统的设计原理和实现方法,掌握构建高性能、高可用、安全可靠的秒杀系统的关键技术,并能够将其应用到实际项目中。

项目开发过程

1. 使用 Maven Archetype 创建新项目

  1. 打开终端/命令提示符:
    输入 mvn -v 来验证安装加粗样式

  2. 运行 Maven 命令:
    使用 Maven 的 Archetype 插件来生成一个新的项目结构。 Maven 提供了多种 Archetype,可以通过 mvn archetype:generate 命令查看和选择不同的模板。运行以下命令:

    mvn archetype:generate -DgroupId=com.example -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
    

    这里的参数解释:

    • -DgroupId=com.example:指定项目的组 ID,一般是公司域名的倒序。
    • -DartifactId=my-app:指定项目的唯一标识符。
    • -DarchetypeArtifactId=maven-archetype-quickstart:使用的原型模板,maven-archetype-quickstart 是一个简单的 Java 项目模板。
    • -DinteractiveMode=false:非交互模式,避免在执行过程中询问输入。
  3. 项目结构:
    运行上述命令后,Maven 会创建一个目录结构如下:

    my-app
    ├── pom.xml
    └── src
        ├── main
        │   └── java
        │       └── com
        │           └── example
        │               └── App.java
        └── test
            └── java
                └── com
                    └── example
                        └── AppTest.java
    
  4. 项目配置文件 (pom.xml):
    pom.xml 是 Maven 项目的核心配置文件,包含了项目的基本信息、依赖管理、构建信息等。

  5. 构建和运行项目:

    • 构建项目: 在项目根目录下运行 mvn package,Maven 会编译代码并打包成一个 JAR 文件。
    • 运行项目: 打包完成后,可以运行生成的 JAR 文件,或者直接使用 mvn springboot:run 来运行(需要在 pom.xml 中配置 springboot-maven-plugin 插件)。

2.逐步写出项目结构和依赖配置:

  1. Maven依赖 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>seckill-system</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
    </parent>

    <properties>
        <java.version>11</java.version>
        <kafka.version>3.3.1</kafka.version>
        <redisson.version>3.17.0</redisson.version>
        <guava.version>31.1-jre</guava.version>
    </properties>

    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Kafka -->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

        <!-- Database -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- Redis -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>${redisson.version}</version>
        </dependency>

        <!-- Utils -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  1. 项目结构
seckill-system/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/seckill/
│   │   │       ├── SeckillApplication.java
│   │   │       ├── config/
│   │   │       │   ├── KafkaConfig.java
│   │   │       │   ├── RedisConfig.java
│   │   │       │   ├── RateLimitConfig.java
│   │   │       │   └── WebConfig.java
│   │   │       ├── controller/
│   │   │       │   └── SeckillController.java
│   │   │       ├── service/
│   │   │       │   ├── SeckillService.java
│   │   │       │   ├── OrderService.java
│   │   │       │   └── StockService.java
│   │   │       ├── consumer/
│   │   │       │   └── SeckillConsumer.java
│   │   │       ├── domain/
│   │   │       │   ├── Order.java
│   │   │       │   ├── Product.java
│   │   │       │   └── SeckillMessage.java
│   │   │       ├── repository/
│   │   │       │   ├── OrderRepository.java
│   │   │       │   └── ProductRepository.java
│   │   │       ├── exception/
│   │   │       │   ├── SeckillException.java
│   │   │       │   └── GlobalExceptionHandler.java
│   │   │       ├── monitor/
│   │   │       │   ├── SeckillMonitor.java
│   │   │       │   └── AlertService.java
│   │   │       └── util/
│   │   │           ├── JsonUtil.java
│   │   │           └── RedisUtil.java
│   │   └── resources/
│   │       ├── application.yml
│   │       ├── application-dev.yml
│   │       └── application-prod.yml
│   └── test/
│       └── java/
│           └── com/example/seckill/
│               ├── service/
│               │   └── SeckillServiceTest.java
│               └── controller/
│                   └── SeckillControllerTest.java
├── pom.xml
└── README.md
  1. 配置文件 (application.yml)
spring:
  application:
    name: seckill-system
    
  datasource:
    url: jdbc:mysql://localhost:3306/seckill?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    
  redis:
    host: localhost
    port: 6379
    
  kafka:
    bootstrap-servers: localhost:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      batch-size: 16384
      buffer-memory: 33554432
    consumer:
      group-id: seckill-group
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      enable-auto-commit: false

server:
  port: 8080
  
management:
  endpoints:
    web:
      exposure:
        include: "*"
  metrics:
    tags:
      application: ${spring.application.name}
      
seckill:
  rateLimit:
    enabled: true
    permits-per-second: 1000
  order:
    timeout-minutes: 30

3.开始开发主要代码

不是全部代码,只是举例说明

这个秒杀系统实现了:

  1. 高并发支持(Kafka削峰)

  2. 库存一致性保证

  3. 防重复提交

  4. 限流保护

  5. 系统架构设计

@Configuration
public class SeckillConfig {
    // 秒杀相关的Topic配置
    public static final String SECKILL_TOPIC = "seckill-orders";
    public static final String DEAD_LETTER_TOPIC = "seckill-dead-letter";
    public static final String GROUP_ID = "seckill-group";
    
    @Bean
    public NewTopic seckillTopic() {
        // 创建秒杀订单Topic,设置分区数和副本数
        return TopicBuilder.name(SECKILL_TOPIC)
                .partitions(3)
                .replicas(2)
                .build();
    }
}
  1. 库存预热和检查
@Service
@Slf4j
public class StockService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String STOCK_KEY = "seckill:stock:";
    private static final String LOCK_KEY = "seckill:lock:";
    
    // 预热库存到Redis
    public void preloadStock(Long productId, Integer stock) {
        String key = STOCK_KEY + productId;
        redisTemplate.opsForValue().set(key, String.valueOf(stock));
    }
    
    // 检查并扣减库存
    public boolean checkAndDeductStock(Long productId) {
        String key = STOCK_KEY + productId;
        String lockKey = LOCK_KEY + productId;
        
        // 分布式锁
        try {
            Boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
            
            if (Boolean.TRUE.equals(locked)) {
                // 检查库存
                Long stock = redisTemplate.opsForValue()
                    .decrement(key);
                
                if (stock != null && stock >= 0) {
                    return true;
                } else {
                    // 库存不足,恢复库存
                    redisTemplate.opsForValue().increment(key);
                    return false;
                }
            }
        } finally {
            redisTemplate.delete(lockKey);
        }
        return false;
    }
}
  1. 秒杀请求处理
@RestController
@RequestMapping("/seckill")
@Slf4j
public class SeckillController {
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    @Autowired
    private StockService stockService;
    
    @PostMapping("/order")
    public ResponseEntity<String> seckill(
            @RequestParam Long productId,
            @RequestParam Long userId) {
        // 1. 防重复请求
        String requestId = String.format("%d_%d", userId, productId);
        if (!checkRepeatedRequest(requestId)) {
            return ResponseEntity.badRequest().body("重复请求");
        }
        
        // 2. 检查库存
        if (!stockService.checkAndDeductStock(productId)) {
            return ResponseEntity.badRequest().body("库存不足");
        }
        
        // 3. 发送消息到Kafka
        SeckillMessage message = new SeckillMessage(userId, productId);
        kafkaTemplate.send(SeckillConfig.SECKILL_TOPIC, 
                         JsonUtil.toJson(message))
            .addCallback(
                result -> log.info("消息发送成功"),
                ex -> log.error("消息发送失败", ex)
            );
        
        return ResponseEntity.ok("订单处理中");
    }
    
    private boolean checkRepeatedRequest(String requestId) {
        return redisTemplate.opsForValue()
            .setIfAbsent(requestId, "1", 1, TimeUnit.MINUTES);
    }
}
  1. 消息消费处理
@Service
@Slf4j
public class SeckillConsumer {
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private StockService stockService;
    
    @KafkaListener(
        topics = SeckillConfig.SECKILL_TOPIC,
        groupId = SeckillConfig.GROUP_ID,
        containerFactory = "seckillKafkaListenerContainerFactory"
    )
    public void handleSeckillMessage(
            @Payload String message,
            @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition,
            @Header(KafkaHeaders.OFFSET) long offset) {
        try {
            SeckillMessage seckillMessage = JsonUtil.fromJson(
                message, SeckillMessage.class);
            
            // 创建订单
            createOrder(seckillMessage);
            
        } catch (Exception e) {
            log.error("处理秒杀消息失败", e);
            // 发送到死信队列
            kafkaTemplate.send(SeckillConfig.DEAD_LETTER_TOPIC, message);
        }
    }
    
    @Transactional
    public void createOrder(SeckillMessage message) {
        // 1. 检查订单是否已存在
        if (orderService.checkOrderExists(message.getUserId(), 
                                        message.getProductId())) {
            return;
        }
        
        // 2. 创建订单
        Order order = new Order();
        order.setUserId(message.getUserId());
        order.setProductId(message.getProductId());
        order.setStatus(OrderStatus.CREATED);
        orderService.createOrder(order);
        
        // 3. 发送订单创建成功的消息
        kafkaTemplate.send("order-created", JsonUtil.toJson(order));
    }
}
  1. 消费者配置优化
@Configuration
public class KafkaConsumerConfig {
    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.GROUP_ID_CONFIG, SeckillConfig.GROUP_ID);
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "50");
        props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, "5000");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        return new DefaultKafkaConsumerFactory<>(props);
    }
    
    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> 
            seckillKafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = 
            new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        // 设置并发消费者数量
        factory.setConcurrency(3);
        // 批量消费
        factory.setBatchListener(true);
        // 手动提交
        factory.getContainerProperties().setAckMode(AckMode.MANUAL);
        return factory;
    }
}
  1. 限流配置
@Configuration
public class RateLimitConfig {
    @Bean
    public RateLimiter rateLimiter() {
        // 每秒允许1000个请求
        return RateLimiter.create(1000);
    }
    
    @Bean
    public RedisRateLimiter redisRateLimiter() {
        return new RedisRateLimiter(redisTemplate);
    }
}

  1. 限流配置
@Aspect
@Component
public class SeckillRateLimiter {
    @Autowired
    private RateLimiter rateLimiter;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String RATE_LIMIT_KEY = "seckill:rate:";
    
    @Around("@annotation(SeckillLimit)")
    public Object limitRate(ProceedingJoinPoint point) throws Throwable {
        // 1. JVM级别限流
        if (!rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) {
            throw new RuntimeException("系统繁忙");
        }
        
        // 2. 分布式限流
        String key = RATE_LIMIT_KEY + getRequestKey(point);
        String count = redisTemplate.opsForValue().get(key);
        if (count != null && Integer.parseInt(count) >= 1000) {
            throw new RuntimeException("访问频率过高");
        }
        
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, 1, TimeUnit.SECONDS);
        
        return point.proceed();
    }
}
  1. 订单处理服务
@Service
@Slf4j
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    @Transactional
    public void createOrder(Order order) {
        try {
            // 1. 保存订单
            orderRepository.save(order);
            
            // 2. 发送订单创建事件
            OrderEvent event = new OrderEvent(
                order.getId(), 
                OrderEventType.CREATED
            );
            kafkaTemplate.send("order-events", JsonUtil.toJson(event));
            
            // 3. 启动订单超时检查
            scheduleOrderTimeout(order);
            
        } catch (Exception e) {
            log.error("创建订单失败", e);
            throw new OrderException("创建订单失败", e);
        }
    }
    
    private void scheduleOrderTimeout(Order order) {
        String key = "order:timeout:" + order.getId();
        redisTemplate.opsForValue().set(key, "1", 30, TimeUnit.MINUTES);
    }
    
    @Scheduled(fixedRate = 60000)
    public void checkTimeoutOrders() {
        Set<String> timeoutKeys = redisTemplate.keys("order:timeout:*");
        for (String key : timeoutKeys) {
            String orderId = key.substring(key.lastIndexOf(":") + 1);
            cancelOrder(Long.parseLong(orderId));
        }
    }
}
  1. 库存一致性处理
@Service
public class StockConsistencyService {
    @Autowired
    private StockRepository stockRepository;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Scheduled(fixedRate = 300000) // 每5分钟执行一次
    public void checkStockConsistency() {
        List<Product> products = stockRepository.findAll();
        
        for (Product product : products) {
            String redisStock = redisTemplate.opsForValue()
                .get(StockService.STOCK_KEY + product.getId());
            
            if (redisStock != null) {
                int dbStock = product.getStock();
                int cacheStock = Integer.parseInt(redisStock);
                
                if (dbStock != cacheStock) {
                    log.warn("库存不一致,商品ID: {}, DB库存: {}, Cache库存: {}",
                        product.getId(), dbStock, cacheStock);
                    
                    // 以数据库为准进行修正
                    redisTemplate.opsForValue()
                        .set(StockService.STOCK_KEY + product.getId(), 
                             String.valueOf(dbStock));
                }
            }
        }
    }
}
  1. 监控和告警
@Component
@Slf4j
public class SeckillMonitor {
    private final Counter seckillCounter = 
        Counter.build()
            .name("seckill_requests_total")
            .help("Total seckill requests")
            .register();
            
    private final Gauge stockGauge = 
        Gauge.build()
            .name("seckill_stock_remaining")
            .help("Remaining stock for seckill")
            .register();
            
    @Autowired
    private AlertService alertService;
    
    public void recordSeckillRequest() {
        seckillCounter.inc();
    }
    
    public void updateStockMetrics(Long productId, int stock) {
        stockGauge.set(stock);
        
        // 库存告警
        if (stock < 10) {
            alertService.sendStockAlert(productId, stock);
        }
    }
    
    @Scheduled(fixedRate = 60000)
    public void checkSystemMetrics() {
        // 检查系统指标
        double successRate = calculateSuccessRate();
        if (successRate < 0.95) {
            alertService.sendSystemAlert("秒杀成功率过低: " + successRate);
        }
    }
}
  1. 性能优化
@Configuration
public class SeckillOptimization {
    @Bean
    public Cache<String, Boolean> localCache() {
        return CacheBuilder.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .build();
    }
    
    @Bean
    public ThreadPoolExecutor asyncExecutor() {
        return new ThreadPoolExecutor(
            10,
            20,
            60L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}
  1. 压测支持
@Component
public class SeckillBenchmark {
    @Autowired
    private WebClient webClient;
    
    public void runBenchmark(int concurrency, int totalRequests) {
        CountDownLatch latch = new CountDownLatch(totalRequests);
        AtomicInteger success = new AtomicInteger(0);
        
        for (int i = 0; i < totalRequests; i++) {
            int userId = i;
            CompletableFuture.runAsync(() -> {
                try {
                    webClient.post()
                        .uri("/seckill/order")
                        .body(BodyInserters.fromFormData("userId", 
                              String.valueOf(userId))
                            .with("productId", "1"))
                        .retrieve()
                        .toBodilessEntity()
                        .block();
                    
                    success.incrementAndGet();
                } catch (Exception e) {
                    log.error("请求失败", e);
                } finally {
                    latch.countDown();
                }
            });
        }
        
        latch.await();
        log.info("压测完成,成功率: {}", 
            (double)success.get() / totalRequests);
    }
}
  1. 启动类
@SpringBootApplication
@EnableScheduling
@EnableAsync
@EnableCaching
public class SeckillApplication {
    public static void main(String[] args) {
        SpringApplication.run(SeckillApplication.class, args);
    }
}
  1. 主要功能类实现

domain/Product.java:

@Entity
@Table(name = "products")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private BigDecimal price;
    private Integer stock;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    
    @Version
    private Integer version;
}

domain/Order.java:

@Entity
@Table(name = "orders")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private Long userId;
    private Long productId;
    private BigDecimal amount;
    
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
    
    private LocalDateTime createTime;
    private LocalDateTime payTime;
}

domain/SeckillMessage.java:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SeckillMessage {
    private Long userId;
    private Long productId;
    private LocalDateTime timestamp;
}

repository/ProductRepository.java:

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select p from Product p where p.id = :id")
    Optional<Product> findByIdWithLock(@Param("id") Long id);
    
    @Modifying
    @Query("update Product p set p.stock = p.stock - 1 where p.id = :id and p.stock > 0")
    int decrementStock(@Param("id") Long id);
}

service/SeckillService.java:

@Service
@Slf4j
public class SeckillService {
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    @Value("${seckill.order.timeout-minutes}")
    private int orderTimeoutMinutes;
    
    public boolean trySecKill(Long userId, Long productId) {
        // 1. 检查用户是否已经购买
        String purchaseKey = String.format("seckill:purchase:%d:%d", userId, productId);
        if (Boolean.TRUE.equals(redisTemplate.hasKey(purchaseKey))) {
            throw new SeckillException("您已经参与过此次秒杀");
        }
        
        // 2. 检查商品是否在秒杀时间内
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new SeckillException("商品不存在"));
            
        if (!isInSeckillTime(product)) {
            throw new SeckillException("不在秒杀时间内");
        }
        
        // 3. 扣减Redis库存
        String stockKey = "seckill:stock:" + productId;
        Long remainStock = redisTemplate.opsForValue().decrement(stockKey);
        
        if (remainStock != null && remainStock >= 0) {
            // 4. 发送创建订单消息
            SeckillMessage message = new SeckillMessage(userId, productId, LocalDateTime.now());
            kafkaTemplate.send("seckill-orders", JsonUtil.toJson(message));
            
            // 5. 标记用户已购买
            redisTemplate.opsForValue().set(purchaseKey, "1", orderTimeoutMinutes, TimeUnit.MINUTES);
            
            return true;
        } else {
            // 库存不足,恢复库存
            redisTemplate.opsForValue().increment(stockKey);
            throw new SeckillException("商品已售罄");
        }
    }
}

util/RedisUtil.java:

@Component
public class RedisUtil {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public boolean tryLock(String key, long timeout, TimeUnit unit) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue()
            .setIfAbsent(key, "1", timeout, unit));
    }
    
    public void unlock(String key) {
        redisTemplate.delete(key);
    }
    
    public boolean tryAcquireToken(String key, int limit) {
        String countStr = redisTemplate.opsForValue().get(key);
        int count = countStr == null ? 0 : Integer.parseInt(countStr);
        
        if (count >= limit) {
            return false;
        }
        
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, 1, TimeUnit.SECONDS);
        return true;
    }
}

monitor/MetricsService.java:

@Service
public class MetricsService {
    private final MeterRegistry registry;
    
    private final Counter seckillCounter;
    private final Counter successCounter;
    private final Timer seckillTimer;
    
    public MetricsService(MeterRegistry registry) {
        this.registry = registry;
        
        this.seckillCounter = Counter.builder("seckill.requests")
            .description("Total seckill requests")
            .register(registry);
            
        this.successCounter = Counter.builder("seckill.success")
            .description("Successful seckill requests")
            .register(registry);
            
        this.seckillTimer = Timer.builder("seckill.latency")
            .description("Seckill request latency")
            .register(registry);
    }
    
    public void recordRequest() {
        seckillCounter.increment();
    }
    
    public void recordSuccess() {
        successCounter.increment();
    }
    
    public Timer.Sample startTimer() {
        return Timer.start(registry);
    }
    
    public void stopTimer(Timer.Sample sample) {
        sample.stop(seckillTimer);
    }
}

exception/SeckillException.java:

public class SeckillException extends RuntimeException {
    public SeckillException(String message) {
        super(message);
    }
    
    public SeckillException(String message, Throwable cause) {
        super(message, cause);
    }
}

utils/JsonUtil.java

  1. 基于 Jackson 的实现(推荐)
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class JsonUtil {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    static {
        // 配置ObjectMapper
        OBJECT_MAPPER.registerModule(new JavaTimeModule()); // 支持Java8时间类型
        OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 日期序列化为ISO格式
        OBJECT_MAPPER.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); // 允许序列化空对象
    }

    private JsonUtil() {
        // 私有构造函数,防止实例化
    }

    /**
     * 对象转JSON字符串
     */
    public static String toJson(Object obj) {
        if (obj == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.writeValueAsString(obj);
        } catch (Exception e) {
            log.error("Convert object to json error", e);
            throw new RuntimeException("Convert object to json error", e);
        }
    }

    /**
     * JSON字符串转对象
     */
    public static <T> T fromJson(String json, Class<T> clazz) {
        if (json == null || json.isEmpty()) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(json, clazz);
        } catch (Exception e) {
            log.error("Parse json to object error", e);
            throw new RuntimeException("Parse json to object error", e);
        }
    }

    /**
     * JSON字符串转复杂对象(如List<T>)
     */
    public static <T> T fromJson(String json, TypeReference<T> typeReference) {
        if (json == null || json.isEmpty()) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(json, typeReference);
        } catch (Exception e) {
            log.error("Parse json to object error", e);
            throw new RuntimeException("Parse json to object error", e);
        }
    }

    /**
     * 对象转byte数组
     */
    public static byte[] toBytes(Object obj) {
        if (obj == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.writeValueAsBytes(obj);
        } catch (Exception e) {
            log.error("Convert object to bytes error", e);
            throw new RuntimeException("Convert object to bytes error", e);
        }
    }

    /**
     * byte数组转对象
     */
    public static <T> T fromBytes(byte[] bytes, Class<T> clazz) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(bytes, clazz);
        } catch (Exception e) {
            log.error("Parse bytes to object error", e);
            throw new RuntimeException("Parse bytes to object error", e);
        }
    }

    /**
     * 对象转换
     */
    public static <T> T convert(Object obj, Class<T> clazz) {
        if (obj == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.convertValue(obj, clazz);
        } catch (Exception e) {
            log.error("Convert object error", e);
            throw new RuntimeException("Convert object error", e);
        }
    }

    /**
     * 格式化JSON字符串
     */
    public static String formatJson(String json) {
        if (json == null || json.isEmpty()) {
            return json;
        }
        try {
            Object obj = OBJECT_MAPPER.readValue(json, Object.class);
            return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (Exception e) {
            log.error("Format json error", e);
            throw new RuntimeException("Format json error", e);
        }
    }

    /**
     * 判断字符串是否是合法的JSON
     */
    public static boolean isValidJson(String json) {
        if (json == null || json.isEmpty()) {
            return false;
        }
        try {
            OBJECT_MAPPER.readTree(json);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}
  1. 使用示例
// 基本使用
public class JsonUtilTest {
    @Test
    public void testJsonUtil() {
        // 对象转JSON
        User user = new User(1L, "张三", 20);
        String json = JsonUtil.toJson(user);
        
        // JSON转对象
        User parsedUser = JsonUtil.fromJson(json, User.class);
        
        // 转换List
        List<User> users = Arrays.asList(
            new User(1L, "张三", 20),
            new User(2L, "李四", 25)
        );
        String listJson = JsonUtil.toJson(users);
        List<User> parsedUsers = JsonUtil.fromJson(listJson, 
            new TypeReference<List<User>>() {});
            
        // 对象转换
        UserDTO userDTO = JsonUtil.convert(user, UserDTO.class);
        
        // 格式化JSON
        String prettyJson = JsonUtil.formatJson(json);
        
        // 验证JSON
        boolean isValid = JsonUtil.isValidJson(json);
    }
}

// 配合Spring使用
@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return objectMapper;
    }
}
  1. 扩展功能(根据需求添加)
public class JsonUtil {
    // ... 前面的代码 ...

    /**
     * 合并两个JSON对象
     */
    public static String mergeJson(String json1, String json2) {
        try {
            JsonNode node1 = OBJECT_MAPPER.readTree(json1);
            JsonNode node2 = OBJECT_MAPPER.readTree(json2);
            
            if (node1.isObject() && node2.isObject()) {
                ObjectNode result = (ObjectNode) node1;
                result.setAll((ObjectNode) node2);
                return OBJECT_MAPPER.writeValueAsString(result);
            }
            throw new IllegalArgumentException("Both arguments must be JSON objects");
        } catch (Exception e) {
            log.error("Merge json error", e);
            throw new RuntimeException("Merge json error", e);
        }
    }

    /**
     * 从JSON中获取指定路径的值
     */
    public static JsonNode getPath(String json, String path) {
        try {
            JsonNode root = OBJECT_MAPPER.readTree(json);
            return root.at(path);
        } catch (Exception e) {
            log.error("Get json path error", e);
            throw new RuntimeException("Get json path error", e);
        }
    }

}

在中引入和配置 SpringDoc OpenAPI:

<!-- 或者使用 SpringDoc OpenAPI (推荐) -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.6.15</version>
</dependency>
  1. apidoc配置类
@Configuration
public class openapiConfig {
    @Bean
    public OpenAPI springShopOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("秒杀系统 API")
                        .description("秒杀系统接口文档")
                        .version("v1.0.0")
                        .contact(new Contact()
                            .name("作者名")
                            .email("[email protected]"))
                        .license(new License()
                            .name("Apache 2.0")
                            .url("http://springdoc.org")))
                .externalDocs(new ExternalDocumentation()
                        .description("项目Wiki文档")
                        .url("https://wiki.example.com"));
    }
    
    // 配置分组
    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group("public")
                .pathsToMatch("/api/public/**")
                .build();
    }

    @Bean
    public GroupedOpenApi adminApi() {
        return GroupedOpenApi.builder()
                .group("admin")
                .pathsToMatch("/api/admin/**")
                .addOpenApiMethodFilter(method -> method.isAnnotationPresent(Admin.class))
                .build();
    }
}
  1. Controller 示例
@Tag(name = "秒杀接口", description = "秒杀相关接口")
@RestController
@RequestMapping("/api/seckill")
@Validated
public class SeckillController {

    @Operation(summary = "执行秒杀", description = "用户执行秒杀操作")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "秒杀成功"),
        @ApiResponse(responseCode = "400", description = "参数错误"),
        @ApiResponse(responseCode = "500", description = "系统错误")
    })
    @PostMapping("/doSeckill")
    public ResponseEntity<SeckillResult> doSeckill(
            @Parameter(description = "用户ID", required = true)
            @RequestParam Long userId,
            
            @Parameter(description = "商品ID", required = true)
            @RequestParam Long productId) {
        // 处理逻辑
        return ResponseEntity.ok(new SeckillResult());
    }

    @Operation(summary = "查询秒杀结果")
    @GetMapping("/result/{orderId}")
    public ResponseEntity<OrderResult> queryResult(
            @Parameter(description = "订单ID", required = true)
            @PathVariable String orderId) {
        // 处理逻辑
        return ResponseEntity.ok(new OrderResult());
    }
}
  1. Model 示例
@Schema(description = "秒杀请求参数")
@Data
public class SeckillRequest {
    @Schema(description = "用户ID", example = "12345")
    @NotNull(message = "用户ID不能为空")
    private Long userId;

    @Schema(description = "商品ID", example = "67890")
    @NotNull(message = "商品ID不能为空")
    private Long productId;

    @Schema(description = "秒杀时间", example = "2023-12-25 10:00:00")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime seckillTime;
}

@Schema(description = "秒杀结果")
@Data
public class SeckillResult {
    @Schema(description = "订单ID", example = "ORDER_123456")
    private String orderId;

    @Schema(description = "秒杀状态", example = "SUCCESS")
    private SeckillStatus status;

    @Schema(description = "错误信息")
    private String errorMsg;
}
  1. 配置文件 (application.yml)
springdoc:
  api-docs:
    enabled: true
    path: /api-docs
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: method
  packages-to-scan: com.example.seckill.controller
  paths-to-match: /api/**
  1. 全局异常处理与响应
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(SeckillException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @Operation(summary = "秒杀异常处理")
    public ResponseEntity<ErrorResponse> handleSeckillException(SeckillException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.BAD_REQUEST.value(),
            ex.getMessage()
        );
        return ResponseEntity.badRequest().body(error);
    }
}

@Schema(description = "错误响应")
@Data
@AllArgsConstructor
public class ErrorResponse {
    @Schema(description = "错误码", example = "400")
    private int code;

    @Schema(description = "错误信息")
    private String message;
}
  1. 安全配置(如果使用Spring Security)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(
            "/swagger-ui/**",
            "/swagger-resources/**",
            "/api-docs/**"
        );
    }
}
  1. 访问方式
# Swagger UI
http://localhost:8080/swagger-ui.html

# OpenAPI JSON
http://localhost:8080/api-docs
  1. 常用注解说明
@Tag                  // 标记控制器类
@Operation            // 标记接口方法
@Parameter            // 描述参数
@Schema              // 描述模型
@ApiResponse         // 描述响应
@SecurityRequirement // 描述安全要求
  1. 测试支持
@SpringBootTest
@AutoConfigureMockMvc
class SeckillControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldGenerateSwaggerDocs() throws Exception {
        mockMvc.perform(get("/api-docs"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.info.title").value("秒杀系统 API"))
               .andDo(document("swagger"));
    }
}

使用 Swagger 的好处:

  1. 提供交互式API文档
  2. 支持API测试
  3. 文档随代码更新
  4. 支持多种开发语言的客户端生成

建议:

  1. 在开发环境启用Swagger
  2. 在生产环境禁用Swagger
  3. 详细描述API参数和响应
  4. 使用适当的安全配置

标签:java,String,private,project,clean,new,return,public
From: https://blog.csdn.net/jsjbrdzhh/article/details/143610861

相关文章

  • 基于Java+SpringBoot心理测评心理测试系统功能实现八
    一、前言介绍:1.1项目摘要心理测评和心理测试系统在当代社会中扮演着越来越重要的角色。随着心理健康问题日益受到重视,心理测评和心理测试系统作为评估个体心理状态、诊断心理问题、制定心理治疗方案的工具,其需求和应用范围不断扩大。首先,现代社会节奏快速,竞争激烈,人们面临着来......
  • 基于Java+SpringBoot心理测评心理测试系统功能实现七
    一、前言介绍:1.1项目摘要心理测评和心理测试系统在当代社会中扮演着越来越重要的角色。随着心理健康问题日益受到重视,心理测评和心理测试系统作为评估个体心理状态、诊断心理问题、制定心理治疗方案的工具,其需求和应用范围不断扩大。首先,现代社会节奏快速,竞争激烈,人们面临着来......
  • java计算机毕业设计智能导诊系统(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容一、研究背景在当今社会,医疗服务的需求不断增长且日益复杂。随着人口的增长、老龄化程度的加深以及人们对健康关注度的提升,医院面临着巨大的就诊压力。患者在......
  • java计算机毕业设计自动排课系统(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容一、研究背景随着教育事业的不断发展,学校规模不断扩大,课程设置日益多样化,传统的人工排课方式面临着巨大的挑战。在各类教育机构中,无论是中小学、高等院校还是......
  • Regex 历史 / 规范 / 流派 | JavaScript 匹配 emoji
    注:本文为几篇regex相关合辑。机翻,未校,未整理。RegexHistoryandHow-ToCrystalVillanuevaJan14,2021Aregularexpression,alsoknownasregexorregexp,isaspecialstringthatpresentsitselfrepeatedlyinasearchpattern;today,programmersuse......
  • Java中的动态代理
    动态代理是Java语言中的一个重要特性,它允许在运行时创建代理类的实例,而不需要在编译时确定具体的类。动态代理通常用于为对象提供额外的功能,比如日志记录、事务管理、权限控制等,而不需要修改目标对象的代码。在Java中,动态代理的实现主要依赖于java.lang.reflect.Proxy类和I......
  • Java代理之Java Agent分析
    目录1JavaAgent1.1简介1.1.1定义1.1.2与代理区别1.1.3主要功能和用途1.2原理和模式1.3使用实现1.3.1Premain模式1.3.1.1创建Agent类1.3.1.2配置Maven1.3.1.3启动程序时指定1.3.2Agentmain模式1.3.2.1通过AttachAPI动态注入1.3.2.2启动Agent1.4Instrumentation......
  • 【java类的生命周期】
    java类的生命周期大阶段加载>使用>卸载其中加载阶段分为加载>链接>初始化链接过程包含:验证>准备>解析加载阶段加载>链接(验证->准备->解析)>初始化加载将.class文件加载到jvm中,这个阶段,jvm根据类的全限定名称获取定义该类的二进制字节流,......
  • JAVA毕业设计198—基于Java+Springboot+vue3的健身房管理系统(源代码+数据库)
    毕设所有选题:https://blog.csdn.net/2303_76227485/article/details/131104075基于Java+Springboot+vue3的健身房管理系统(源代码+数据库)198一、系统介绍本项目前后端分离(可以改为ssm版本),分为用户、管理员两种角色1、用户:注册、登录、公告、论坛交流、健身课程购买......
  • Java流程控制-循环结构
    循环结构while循环while是最基本的循环,它的结构为:while(布尔表达式){ //循环内容}只要布尔表达式为true,循环就会一直执行下去。我们大多数情况是会让循环停止下来的,我们需要一个让表达式失效的方式来结束循环。少部分情况需要循环一直执行,比如服务器的请求响应监听等。......