Browse Source

0518 查询车票逻辑

Qing 1 year ago
parent
commit
9164759099
36 changed files with 1046 additions and 15 deletions
  1. 44 0
      12306-demo/my-service/pom.xml
  2. 4 0
      12306-demo/my-service/src/main/java/com/sf/ServiceApplication.java
  3. 30 0
      12306-demo/my-service/src/main/java/com/sf/constant/Index12306Constant.java
  4. 145 0
      12306-demo/my-service/src/main/java/com/sf/constant/RedisKeyConstant.java
  5. 14 0
      12306-demo/my-service/src/main/java/com/sf/controller/MyController.java
  6. 25 0
      12306-demo/my-service/src/main/java/com/sf/controller/TicketController.java
  7. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/builder/Builder.java
  8. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/builder/Main.java
  9. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/builder/MyBuilder.java
  10. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/builder/Product.java
  11. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/chain/CEO.java
  12. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/chain/Handler.java
  13. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/chain/Leader.java
  14. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/chain/Main.java
  15. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/chain/Manager.java
  16. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/strategy/Main.java
  17. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/strategy/MyStrategyA.java
  18. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/strategy/MyStrategyB.java
  19. 1 1
      12306-demo/my-service/src/main/java/com/sf/design/strategy/Strategy.java
  20. 56 0
      12306-demo/my-service/src/main/java/com/sf/dto/SeatClassDTO.java
  21. 110 0
      12306-demo/my-service/src/main/java/com/sf/dto/TicketListDTO.java
  22. 58 0
      12306-demo/my-service/src/main/java/com/sf/dto/req/TicketPageQueryReqDTO.java
  23. 63 0
      12306-demo/my-service/src/main/java/com/sf/dto/resp/TicketPageQueryRespDTO.java
  24. 72 0
      12306-demo/my-service/src/main/java/com/sf/entity/TicketDO.java
  25. 61 0
      12306-demo/my-service/src/main/java/com/sf/entity/TrainStationPriceDO.java
  26. 34 0
      12306-demo/my-service/src/main/java/com/sf/filter/CustomFilter.java
  27. 29 0
      12306-demo/my-service/src/main/java/com/sf/filter/CustomFilter2.java
  28. 21 0
      12306-demo/my-service/src/main/java/com/sf/filter/config/FilterConfig.java
  29. 32 0
      12306-demo/my-service/src/main/java/com/sf/interceptor/CustomInterceptor.java
  30. 23 0
      12306-demo/my-service/src/main/java/com/sf/interceptor/config/InterceptorConfig.java
  31. 29 0
      12306-demo/my-service/src/main/java/com/sf/mapper/TicketMapper.java
  32. 17 0
      12306-demo/my-service/src/main/java/com/sf/service/TicketService.java
  33. 128 0
      12306-demo/my-service/src/main/java/com/sf/service/impl/TicketServiceImpl.java
  34. 25 0
      12306-demo/my-service/src/main/java/com/sf/util/TimeStringComparator.java
  35. 12 1
      12306-demo/my-service/src/main/resources/application.yml
  36. 1 1
      12306-demo/pom.xml

+ 44 - 0
12306-demo/my-service/pom.xml

@@ -15,6 +15,7 @@
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <index12306.version>0.0.1-SNAPSHOT</index12306.version>
     </properties>
 
     <dependencies>
@@ -39,5 +40,48 @@
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
+
+
+        <dependency>
+            <groupId>org.opengoofy.index12306</groupId>
+            <artifactId>index12306-database-spring-boot-starter</artifactId>
+            <version>${index12306.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.opengoofy.index12306</groupId>
+            <artifactId>index12306-user-spring-boot-starter</artifactId>
+            <version>${index12306.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.opengoofy.index12306</groupId>
+            <artifactId>index12306-web-spring-boot-starter</artifactId>
+            <version>${index12306.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.opengoofy.index12306</groupId>
+            <artifactId>index12306-cache-spring-boot-starter</artifactId>
+            <version>${index12306.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.opengoofy.index12306</groupId>
+            <artifactId>index12306-convention-spring-boot-starter</artifactId>
+            <version>${index12306.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.opengoofy.index12306</groupId>
+            <artifactId>index12306-base-spring-boot-starter</artifactId>
+            <version>${index12306.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.opengoofy.index12306</groupId>
+            <artifactId>index12306-log-spring-boot-starter</artifactId>
+            <version>${index12306.version}</version>
+        </dependency>
     </dependencies>
 </project>

+ 4 - 0
12306-demo/my-service/src/main/java/com/sf/ServiceApplication.java

@@ -1,11 +1,15 @@
 package com.sf;
 
 import com.sf.config.MyEnableAutoConfig;
+import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletComponentScan;
 
 @MyEnableAutoConfig
 @SpringBootApplication
+@ServletComponentScan
+@MapperScan("com.sf.mapper")
 public class ServiceApplication {
 
     public static void main(String[] args) {

+ 30 - 0
12306-demo/my-service/src/main/java/com/sf/constant/Index12306Constant.java

@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.constant;
+
+/**
+ * 系统级公共常量
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public final class Index12306Constant {
+
+    /**
+     * 提前买票天数
+     */
+    public static final int ADVANCE_TICKET_DAY = 15;
+}

+ 145 - 0
12306-demo/my-service/src/main/java/com/sf/constant/RedisKeyConstant.java

@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.constant;
+
+/**
+ * Redis Key 定义常量类
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public final class RedisKeyConstant {
+
+    /**
+     * 列车基本信息,Key Prefix + 列车ID
+     */
+    public static final String TRAIN_INFO = "index12306-ticket-service:train_info:";
+
+    /**
+     * 地区与站点映射查询
+     */
+    public static final String REGION_TRAIN_STATION_MAPPING = "index12306-ticket-service:region_train_station_mapping";
+
+    /**
+     * 站点查询分布式锁 Key
+     */
+    public static final String LOCK_REGION_TRAIN_STATION_MAPPING = "index12306-ticket-service:lock:region_train_station_mapping";
+
+    /**
+     * 站点查询,Key Prefix + 起始城市_终点城市_日期
+     */
+    public static final String REGION_TRAIN_STATION = "index12306-ticket-service:region_train_station:%s_%s";
+
+    /**
+     * 站点查询分布式锁 Key
+     */
+    public static final String LOCK_REGION_TRAIN_STATION = "index12306-ticket-service:lock:region_train_station";
+
+    /**
+     * 列车站点座位价格查询,Key Prefix + 列车ID_起始城市_终点城市
+     */
+    public static final String TRAIN_STATION_PRICE = "index12306-ticket-service:train_station_price:%s_%s_%s";
+
+    /**
+     * 地区以及车站查询,Key Prefix + ( 车站名称 or 查询方式 )
+     */
+    public static final String REGION_STATION = "index12306-ticket-service:region-station:";
+
+    /**
+     * 站点余票查询,Key Prefix + 列车ID_起始站点_终点
+     */
+    public static final String TRAIN_STATION_REMAINING_TICKET = "index12306-ticket-service:train_station_remaining_ticket:";
+
+    /**
+     * 列车车厢查询,Key Prefix + 列车ID
+     */
+    public static final String TRAIN_CARRIAGE = "index12306-ticket-service:train_carriage:";
+
+    /**
+     * 车厢余票查询,Key Prefix + 列车ID_起始站点_终点
+     */
+    public static final String TRAIN_STATION_CARRIAGE_REMAINING_TICKET = "index12306-ticket-service:train_station_carriage_remaining_ticket:";
+
+    /**
+     * 站点详细信息查询,Key Prefix + 列车ID_起始站点_终点
+     */
+    public static final String TRAIN_STATION_DETAIL = "index12306-ticket-service:train_station_detail:";
+
+    /**
+     * 列车路线信息查询,Key Prefix + 列车ID
+     */
+    public static final String TRAIN_STATION_STOPOVER_DETAIL = "index12306-ticket-service:train_station_stopover_detail:";
+
+    /**
+     * 列车站点缓存
+     */
+    public static final String STATION_ALL = "index12306-ticket-service:all_station";
+
+    /**
+     * 列车车厢状态, Key Prefix + 列车 ID + 起始站点 + 目的站点 + 车厢编号
+     */
+    public static final String TRAIN_CARRIAGE_SEAT_STATUS = "index12306-ticket-service:train_carriage_seat_status:";
+
+    /**
+     * 用户购票分布式锁 Key
+     */
+    public static final String LOCK_PURCHASE_TICKETS = "${unique-name:}index12306-ticket-service:lock:purchase_tickets_%s";
+
+    /**
+     * 用户购票分布式锁 Key v2
+     */
+    public static final String LOCK_PURCHASE_TICKETS_V2 = "${unique-name:}index12306-ticket-service:lock:purchase_tickets_%s_%d";
+
+    /**
+     * 获取全部地点集合 Key
+     */
+    public static final String QUERY_ALL_REGION_LIST = "index12306-ticket-service:query_all_region_list";
+
+    /**
+     * 列车购买令牌桶,Key Prefix + 列车ID
+     */
+    public static final String TICKET_AVAILABILITY_TOKEN_BUCKET = "index12306-ticket-service:ticket_availability_token_bucket:";
+
+    /**
+     * 获取全部地点集合分布式锁 Key
+     */
+    public static final String LOCK_QUERY_ALL_REGION_LIST = "index12306-ticket-service:lock:query_all_region_list";
+
+    /**
+     * 获取列车车厢数量集合分布式锁 Key
+     */
+    public static final String LOCK_QUERY_CARRIAGE_NUMBER_LIST = "index12306-ticket-service:lock:query_carriage_number_list_%s";
+
+    /**
+     * 获取地区以及站点集合分布式锁 Key
+     */
+    public static final String LOCK_QUERY_REGION_STATION_LIST = "index12306-ticket-service:lock:query_region_station_list_%s";
+
+    /**
+     * 获取相邻座位余票分布式锁 Key
+     */
+    public static final String LOCK_SAFE_LOAD_SEAT_MARGIN_GET = "index12306-ticket-service:lock:safe_load_seat_margin_%s";
+
+    /**
+     * 列车购买令牌桶加载数据 Key
+     */
+    public static final String LOCK_TICKET_AVAILABILITY_TOKEN_BUCKET = "index12306-ticket-service:lock:ticket_availability_token_bucket:%s";
+
+    /**
+     * 令牌获取失败分布式锁 Key
+     */
+    public static final String LOCK_TOKEN_BUCKET_ISNULL = "index12306-ticket-service:lock:token-bucket-isnull:%s";
+}

+ 14 - 0
12306-demo/my-service/src/main/java/com/sf/controller/MyController.java

@@ -0,0 +1,14 @@
+package com.sf.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class MyController {
+
+    @GetMapping("/test")
+    public String test(){
+        System.out.println("MyController test");
+        return "test";
+    }
+}

+ 25 - 0
12306-demo/my-service/src/main/java/com/sf/controller/TicketController.java

@@ -0,0 +1,25 @@
+package com.sf.controller;
+
+import com.sf.dto.req.TicketPageQueryReqDTO;
+import com.sf.dto.resp.TicketPageQueryRespDTO;
+import com.sf.service.TicketService;
+import org.opengoofy.index12306.framework.starter.convention.result.Result;
+import org.opengoofy.index12306.framework.starter.web.Results;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class TicketController {
+
+    @Autowired
+    private TicketService ticketService;
+
+    // http://localhost:9000/api/ticket-service/ticket/query?fromStation=BJP&toStation=HZH&departureDate=2024-05-19
+    @GetMapping("/api/ticket-service/ticket/query")
+    public Result<TicketPageQueryRespDTO> pageListTicketQuery(TicketPageQueryReqDTO requestParam) {
+        System.out.println("TicketController pageListTicketQuery");
+        return Results.success(ticketService.pageListTicketQueryV1(requestParam));
+    }
+
+}

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/builder/Builder.java → 12306-demo/my-service/src/main/java/com/sf/design/builder/Builder.java

@@ -1,4 +1,4 @@
-package com.sf.builder;
+package com.sf.design.builder;
 
 /**
  * 抽象的构建者

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/builder/Main.java → 12306-demo/my-service/src/main/java/com/sf/design/builder/Main.java

@@ -1,4 +1,4 @@
-package com.sf.builder;
+package com.sf.design.builder;
 
 public class Main {
 

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/builder/MyBuilder.java → 12306-demo/my-service/src/main/java/com/sf/design/builder/MyBuilder.java

@@ -1,4 +1,4 @@
-package com.sf.builder;
+package com.sf.design.builder;
 
 
 public class MyBuilder implements Builder{

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/builder/Product.java → 12306-demo/my-service/src/main/java/com/sf/design/builder/Product.java

@@ -1,4 +1,4 @@
-package com.sf.builder;
+package com.sf.design.builder;
 
 import lombok.Data;
 

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/chain/CEO.java → 12306-demo/my-service/src/main/java/com/sf/design/chain/CEO.java

@@ -1,4 +1,4 @@
-package com.sf.chain;
+package com.sf.design.chain;
 
 public class CEO implements Handler{
 

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/chain/Handler.java → 12306-demo/my-service/src/main/java/com/sf/design/chain/Handler.java

@@ -1,4 +1,4 @@
-package com.sf.chain;
+package com.sf.design.chain;
 
 // 处理器
 public interface Handler {

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/chain/Leader.java → 12306-demo/my-service/src/main/java/com/sf/design/chain/Leader.java

@@ -1,4 +1,4 @@
-package com.sf.chain;
+package com.sf.design.chain;
 
 public class Leader implements Handler{
     // 需要下一个处理器  所以需要创建一个属性

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/chain/Main.java → 12306-demo/my-service/src/main/java/com/sf/design/chain/Main.java

@@ -1,4 +1,4 @@
-package com.sf.chain;
+package com.sf.design.chain;
 
 public class Main {
 

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/chain/Manager.java → 12306-demo/my-service/src/main/java/com/sf/design/chain/Manager.java

@@ -1,4 +1,4 @@
-package com.sf.chain;
+package com.sf.design.chain;
 
 public class Manager implements Handler {
 

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/strategy/Main.java → 12306-demo/my-service/src/main/java/com/sf/design/strategy/Main.java

@@ -1,4 +1,4 @@
-package com.sf.strategy;
+package com.sf.design.strategy;
 
 public class Main {
 

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/strategy/MyStrategyA.java → 12306-demo/my-service/src/main/java/com/sf/design/strategy/MyStrategyA.java

@@ -1,4 +1,4 @@
-package com.sf.strategy;
+package com.sf.design.strategy;
 
 public class MyStrategyA extends Strategy{
 

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/strategy/MyStrategyB.java → 12306-demo/my-service/src/main/java/com/sf/design/strategy/MyStrategyB.java

@@ -1,4 +1,4 @@
-package com.sf.strategy;
+package com.sf.design.strategy;
 
 public class MyStrategyB extends Strategy{
 

+ 1 - 1
12306-demo/my-service/src/main/java/com/sf/strategy/Strategy.java → 12306-demo/my-service/src/main/java/com/sf/design/strategy/Strategy.java

@@ -1,4 +1,4 @@
-package com.sf.strategy;
+package com.sf.design.strategy;
 
 // 策略抽象类
 public abstract class Strategy {

+ 56 - 0
12306-demo/my-service/src/main/java/com/sf/dto/SeatClassDTO.java

@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 席别类型实体
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class SeatClassDTO {
+
+    /**
+     * 席别类型
+     */
+    private Integer type;
+
+    /**
+     * 席别数量
+     */
+    private Integer quantity;
+
+    /**
+     * 席别价格
+     */
+    private BigDecimal price;
+
+    /**
+     * 席别候补标识
+     */
+    private Boolean candidate;
+}

+ 110 - 0
12306-demo/my-service/src/main/java/com/sf/dto/TicketListDTO.java

@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 车次集合实体
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+public class TicketListDTO {
+
+    /**
+     * 列车 ID
+     */
+    private String trainId;
+
+    /**
+     * 车次
+     */
+    private String trainNumber;
+
+    /**
+     * 出发时间
+     */
+    private String departureTime;
+
+    /**
+     * 到达时间
+     */
+    private String arrivalTime;
+
+    /**
+     * 历时
+     */
+    private String duration;
+
+    /**
+     * 到达天数
+     */
+    private Integer daysArrived;
+
+    /**
+     * 出发站点
+     */
+    private String departure;
+
+    /**
+     * 到达站点
+     */
+    private String arrival;
+
+    /**
+     * 始发站标识
+     */
+    private Boolean departureFlag;
+
+    /**
+     * 终点站标识
+     */
+    private Boolean arrivalFlag;
+
+    /**
+     * 列车类型 0:高铁 1:动车 2:普通车
+     */
+    private Integer trainType;
+
+    /**
+     * 可售时间
+     */
+    private String saleTime;
+
+    /**
+     * 销售状态 0:可售 1:不可售 2:未知
+     */
+    private Integer saleStatus;
+
+    /**
+     * 列车标签集合 0:复兴号 1:智能动车组 2:静音车厢 3:支持选铺
+     */
+    private List<String> trainTags;
+
+    /**
+     * 列车品牌类型 0:GC-高铁/城际 1:D-动车 2:Z-直达 3:T-特快 4:K-快速 5:其他 6:复兴号 7:智能动车组
+     */
+    private String trainBrand;
+
+    /**
+     * 席别实体集合
+     */
+    private List<SeatClassDTO> seatClassList;
+}

+ 58 - 0
12306-demo/my-service/src/main/java/com/sf/dto/req/TicketPageQueryReqDTO.java

@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.dto.req;
+
+import lombok.Data;
+import org.opengoofy.index12306.framework.starter.convention.page.PageRequest;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * 车票分页查询请求参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+public class TicketPageQueryReqDTO extends PageRequest {
+
+    /**
+     * 出发地 Code
+     */
+    private String fromStation;
+
+    /**
+     * 目的地 Code
+     */
+    private String toStation;
+
+    /**
+     * 出发日期
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private Date departureDate;
+
+    /**
+     * 出发站点
+     */
+    private String departure;
+
+    /**
+     * 到达站点
+     */
+    private String arrival;
+}

+ 63 - 0
12306-demo/my-service/src/main/java/com/sf/dto/resp/TicketPageQueryRespDTO.java

@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.dto.resp;
+
+import com.sf.dto.TicketListDTO;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+import java.util.List;
+
+/**
+ * 车票分页查询响应参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TicketPageQueryRespDTO {
+
+    /**
+     * 车次集合数据
+     */
+    private List<TicketListDTO> trainList;
+
+    /**
+     * 车次类型:D-动车 Z-直达 复兴号等
+     */
+    private List<Integer> trainBrandList;
+
+    /**
+     * 出发车站
+     */
+    private List<String> departureStationList;
+
+    /**
+     * 到达车站
+     */
+    private List<String> arrivalStationList;
+
+    /**
+     * 车次席别
+     */
+    private List<Integer> seatClassTypeList;
+}

+ 72 - 0
12306-demo/my-service/src/main/java/com/sf/entity/TicketDO.java

@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.opengoofy.index12306.framework.starter.database.base.BaseDO;
+
+/**
+ * 车票实体
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@TableName("t_ticket")
+public class TicketDO extends BaseDO {
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 列车id
+     */
+    private Long trainId;
+
+    /**
+     * 车厢号
+     */
+    private String carriageNumber;
+
+    /**
+     * 座位号
+     */
+    private String seatNumber;
+
+    /**
+     * 乘车人 ID
+     */
+    private String passengerId;
+
+    /**
+     * 车票状态
+     */
+    private Integer ticketStatus;
+}

+ 61 - 0
12306-demo/my-service/src/main/java/com/sf/entity/TrainStationPriceDO.java

@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import org.opengoofy.index12306.framework.starter.database.base.BaseDO;
+
+/**
+ * 列车站点价格实体
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@TableName("t_train_station_price")
+public class TrainStationPriceDO extends BaseDO {
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 车次id
+     */
+    private Long trainId;
+
+    /**
+     * 座位类型
+     */
+    private Integer seatType;
+
+    /**
+     * 出发站点
+     */
+    private String departure;
+
+    /**
+     * 到达站点
+     */
+    private String arrival;
+
+    /**
+     * 车票价格
+     */
+    private Integer price;
+}

+ 34 - 0
12306-demo/my-service/src/main/java/com/sf/filter/CustomFilter.java

@@ -0,0 +1,34 @@
+package com.sf.filter;
+
+import jakarta.servlet.*;
+
+import java.io.IOException;
+
+// 过滤: 是根据不同的情况进行筛选
+// 过滤器: 实现原理基于回调函数
+// 使用场景:用户登录、判断url是否有权限、统计在线人数等等
+// 有多种实现方式
+// 1 实现Filter接口
+// 2 @WebFilter
+public class CustomFilter implements Filter {
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        Filter.super.init(filterConfig);
+        System.out.println("CustomFilter init");
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+                         FilterChain filterChain) throws IOException, ServletException {
+        System.out.println("CustomFilter doFilter");
+        filterChain.doFilter(servletRequest, servletResponse);
+        System.out.println("CustomFilter doFilter finish");
+    }
+
+    @Override
+    public void destroy() {
+        Filter.super.destroy();
+        System.out.println("CustomFilter destroy");
+    }
+}

+ 29 - 0
12306-demo/my-service/src/main/java/com/sf/filter/CustomFilter2.java

@@ -0,0 +1,29 @@
+package com.sf.filter;
+
+import jakarta.servlet.*;
+import jakarta.servlet.annotation.WebFilter;
+
+import java.io.IOException;
+
+@WebFilter(filterName = "customFilter2",urlPatterns = {"/*"})
+public class CustomFilter2 implements Filter {
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        Filter.super.init(filterConfig);
+        System.out.println("CustomFilter2 init");
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+        System.out.println("CustomFilter2 doFilter");
+        filterChain.doFilter(servletRequest, servletResponse);
+        System.out.println("CustomFilter2 doFilter finish");
+    }
+
+    @Override
+    public void destroy() {
+        Filter.super.destroy();
+        System.out.println("CustomFilter2 destroy");
+    }
+}

+ 21 - 0
12306-demo/my-service/src/main/java/com/sf/filter/config/FilterConfig.java

@@ -0,0 +1,21 @@
+package com.sf.filter.config;
+
+import com.sf.filter.CustomFilter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+// 通过配置文件 让自定义的filter生效
+@Configuration
+public class FilterConfig {
+
+    @Bean
+    public FilterRegistrationBean<CustomFilter> filterRegistrationBean() {
+        FilterRegistrationBean<CustomFilter> filterRegistrationBean = new FilterRegistrationBean<>();
+        // 创建一个过滤器注册bean,然后将自定义的过滤器设置进去,再配置过滤器对应的url范围
+        filterRegistrationBean.setFilter(new CustomFilter());
+        // /*代表全部url
+        filterRegistrationBean.addUrlPatterns("/*");
+        return filterRegistrationBean;
+    }
+}

+ 32 - 0
12306-demo/my-service/src/main/java/com/sf/interceptor/CustomInterceptor.java

@@ -0,0 +1,32 @@
+package com.sf.interceptor;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+// 拦截:拦住
+// 拦截器:实现原理是基于反射 代理模式
+// 使用场景:用户登录 数据的统计  请求的拦截
+@Component
+public class CustomInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        System.out.println("CustomInterceptor preHandle");
+        return HandlerInterceptor.super.preHandle(request, response, handler);
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
+        System.out.println("CustomInterceptor postHandle");
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
+        System.out.println("CustomInterceptor afterCompletion");
+    }
+}

+ 23 - 0
12306-demo/my-service/src/main/java/com/sf/interceptor/config/InterceptorConfig.java

@@ -0,0 +1,23 @@
+package com.sf.interceptor.config;
+
+import com.sf.interceptor.CustomInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+// WebMvcConfigurer 这个类用于在springboot项目中 修改springmvc相关的配置
+@Configuration
+public class InterceptorConfig implements WebMvcConfigurer {
+
+    @Autowired
+    private CustomInterceptor customInterceptor;
+
+    // 参数registry 是注册中心
+    // addPathPatterns 是拦截的请求路径
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+//        WebMvcConfigurer.super.addInterceptors(registry);
+        registry.addInterceptor(customInterceptor).addPathPatterns("/**");
+    }
+}

+ 29 - 0
12306-demo/my-service/src/main/java/com/sf/mapper/TicketMapper.java

@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sf.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sf.entity.TicketDO;
+
+
+/**
+ * 车票持久层
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public interface TicketMapper extends BaseMapper<TicketDO> {
+}

+ 17 - 0
12306-demo/my-service/src/main/java/com/sf/service/TicketService.java

@@ -0,0 +1,17 @@
+package com.sf.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sf.dto.req.TicketPageQueryReqDTO;
+import com.sf.dto.resp.TicketPageQueryRespDTO;
+import com.sf.entity.TicketDO;
+
+public interface TicketService extends IService<TicketDO> {
+
+    /**
+     * 根据条件分页查询车票
+     *
+     * @param requestParam 分页查询车票请求参数
+     * @return 查询车票返回结果
+     */
+    TicketPageQueryRespDTO pageListTicketQueryV1(TicketPageQueryReqDTO requestParam);
+}

+ 128 - 0
12306-demo/my-service/src/main/java/com/sf/service/impl/TicketServiceImpl.java

@@ -0,0 +1,128 @@
+package com.sf.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.Lists;
+import com.sf.dto.SeatClassDTO;
+import com.sf.dto.TicketListDTO;
+import com.sf.dto.req.TicketPageQueryReqDTO;
+import com.sf.dto.resp.TicketPageQueryRespDTO;
+import com.sf.entity.TicketDO;
+import com.sf.entity.TrainStationPriceDO;
+import com.sf.mapper.TicketMapper;
+import com.sf.service.TicketService;
+import com.sf.util.TimeStringComparator;
+import lombok.RequiredArgsConstructor;
+import org.opengoofy.index12306.framework.starter.cache.DistributedCache;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static com.sf.constant.Index12306Constant.ADVANCE_TICKET_DAY;
+import static com.sf.constant.RedisKeyConstant.*;
+
+@RequiredArgsConstructor
+@Service
+public class TicketServiceImpl extends ServiceImpl<TicketMapper, TicketDO> implements TicketService {
+
+    private final DistributedCache distributedCache;
+
+    // http://localhost:9000/api/ticket-service/ticket/query?fromStation=BJP&toStation=HZH&departureDate=2024-05-15
+    @Override
+    public TicketPageQueryRespDTO pageListTicketQueryV1(TicketPageQueryReqDTO requestParam) {
+
+        StringRedisTemplate stringRedisTemplate = (StringRedisTemplate) distributedCache.getInstance();
+
+        // 先获取 index12306-ticket-service:region_train_station_mapping 中的数据
+        // 得到出发站和目的站的名字
+        List<Object> stationDetails = stringRedisTemplate.opsForHash()
+                .multiGet(REGION_TRAIN_STATION_MAPPING, Lists.newArrayList(requestParam.getFromStation(), requestParam.getToStation()));
+
+        // index12306-ticket-service:region_train_station:%s_%s
+        // 拼接出key  index12306-ticket-service:region_train_station:北京_杭州
+        String buildRegionTrainStationHashKey = String.format(REGION_TRAIN_STATION, stationDetails.get(0), stationDetails.get(1));
+        // 从redis中 取出key存储的value值  对应一个map结果
+        Map<Object, Object> regionTrainStationAllMap = stringRedisTemplate.opsForHash().entries(buildRegionTrainStationHashKey);
+        // 遍历其中每个元素 转化为多个TicketListDTO对象组成的list
+        List<TicketListDTO> seatResults = regionTrainStationAllMap.values().stream().map(
+                each -> JSON.parseObject(each.toString(), TicketListDTO.class)).toList();
+        // 按照出发时间排序
+        seatResults = seatResults.stream().sorted(new TimeStringComparator()).toList();
+        for (TicketListDTO each : seatResults) {
+            // index12306-ticket-service:train_station_price:%s_%s_%s
+            // 拼接成key index12306-ticket-service:train_station_price:1_北京南_杭州东
+            String trainStationPriceStr = distributedCache.safeGet(
+                    String.format(TRAIN_STATION_PRICE, each.getTrainId(), each.getDeparture(), each.getArrival()),
+                    String.class,
+                    null,
+                    ADVANCE_TICKET_DAY,
+                    TimeUnit.DAYS
+            );
+            // 返回结果是由 TrainStationPriceDO对象组成的list 所转化的json数据
+            List<TrainStationPriceDO> trainStationPriceDOList = JSON.parseArray(trainStationPriceStr, TrainStationPriceDO.class);
+            List<SeatClassDTO> seatClassList = new ArrayList<>();
+            // 遍历每一个TrainStationPriceDO
+            trainStationPriceDOList.forEach(item -> {
+                String seatType = String.valueOf(item.getSeatType());
+                // 拼接成key index12306-ticket-service:train_station_remaining_ticket:1_北京南_杭州东
+                String keySuffix = StrUtil.join("_", each.getTrainId(), item.getDeparture(), item.getArrival());
+                // 获取每个席别对应的票数
+                Object quantityObj = stringRedisTemplate.opsForHash().get(TRAIN_STATION_REMAINING_TICKET + keySuffix, seatType);
+                int quantity = Optional.ofNullable(quantityObj).map(Object::toString).map(Integer::parseInt).get();
+                seatClassList.add(new SeatClassDTO(
+                        item.getSeatType(), quantity,
+                        new BigDecimal(item.getPrice()).divide(new BigDecimal("100"), 1, RoundingMode.HALF_UP),
+                        false));
+            });
+            each.setSeatClassList(seatClassList);
+        }
+
+        // 组装数据
+        return TicketPageQueryRespDTO.builder()
+                .trainList(seatResults)
+                .departureStationList(buildDepartureStationList(seatResults))
+                .arrivalStationList(buildArrivalStationList(seatResults))
+                .trainBrandList(buildTrainBrandList(seatResults))
+                .seatClassTypeList(buildSeatClassList(seatResults))
+                .build();
+    }
+
+
+    private List<String> buildDepartureStationList(List<TicketListDTO> seatResults) {
+        return seatResults.stream().map(TicketListDTO::getDeparture).distinct().collect(Collectors.toList());
+    }
+
+    private List<String> buildArrivalStationList(List<TicketListDTO> seatResults) {
+        return seatResults.stream().map(TicketListDTO::getArrival).distinct().collect(Collectors.toList());
+    }
+
+    private List<Integer> buildSeatClassList(List<TicketListDTO> seatResults) {
+        Set<Integer> resultSeatClassList = new HashSet<>();
+        for (TicketListDTO each : seatResults) {
+            for (SeatClassDTO item : each.getSeatClassList()) {
+                resultSeatClassList.add(item.getType());
+            }
+        }
+        return resultSeatClassList.stream().toList();
+    }
+
+    private List<Integer> buildTrainBrandList(List<TicketListDTO> seatResults) {
+        Set<Integer> trainBrandSet = new HashSet<>();
+        for (TicketListDTO each : seatResults) {
+            if (StrUtil.isNotBlank(each.getTrainBrand())) {
+                trainBrandSet.addAll(StrUtil.split(each.getTrainBrand(), ",").stream().map(Integer::parseInt).toList());
+            }
+        }
+        return trainBrandSet.stream().toList();
+    }
+}

+ 25 - 0
12306-demo/my-service/src/main/java/com/sf/util/TimeStringComparator.java

@@ -0,0 +1,25 @@
+
+package com.sf.util;
+
+
+import com.sf.dto.TicketListDTO;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Comparator;
+
+/**
+ * 自定义时间比较器
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public class TimeStringComparator implements Comparator<TicketListDTO> {
+
+    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
+
+    @Override
+    public int compare(TicketListDTO ticketList1, TicketListDTO ticketList2) {
+        LocalTime localTime1 = LocalTime.parse(ticketList1.getDepartureTime(), FORMATTER);
+        LocalTime localTime2 = LocalTime.parse(ticketList2.getDepartureTime(), FORMATTER);
+        return localTime1.compareTo(localTime2);
+    }
+}

+ 12 - 1
12306-demo/my-service/src/main/resources/application.yml

@@ -1,3 +1,14 @@
 sf:
   basePackage: com.sf
-  version: 1.0
+  version: 1.0
+server:
+  port: 9000
+
+spring:
+  application:
+    name: my-ticket-service
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: root123456
+    url: jdbc:mysql://127.0.0.1:3306/railway_ticket?characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8

+ 1 - 1
12306-demo/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>3.2.5</version>
+        <version>3.0.7</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.sf</groupId>