Qing 1 рік тому
батько
коміт
29d83b720d
58 змінених файлів з 3930 додано та 2 видалено
  1. 33 0
      12306-demo/my-order-service/.gitignore
  2. 18 0
      12306-demo/my-order-service/.mvn/wrapper/maven-wrapper.properties
  3. 250 0
      12306-demo/my-order-service/mvnw
  4. 146 0
      12306-demo/my-order-service/mvnw.cmd
  5. 171 0
      12306-demo/my-order-service/pom.xml
  6. 17 0
      12306-demo/my-order-service/src/main/java/com/sf/OrderServiceApplication.java
  7. 25 0
      12306-demo/my-order-service/src/main/java/com/sf/controller/OrderController.java
  8. 91 0
      12306-demo/my-order-service/src/main/java/com/sf/dto/req/TicketOrderCreateReqDTO.java
  9. 78 0
      12306-demo/my-order-service/src/main/java/com/sf/dto/req/TicketOrderItemCreateReqDTO.java
  10. 119 0
      12306-demo/my-order-service/src/main/java/com/sf/entity/OrderDO.java
  11. 112 0
      12306-demo/my-order-service/src/main/java/com/sf/entity/OrderItemDO.java
  12. 57 0
      12306-demo/my-order-service/src/main/java/com/sf/entity/OrderItemPassengerDO.java
  13. 62 0
      12306-demo/my-order-service/src/main/java/com/sf/enums/OrderStatusEnum.java
  14. 28 0
      12306-demo/my-order-service/src/main/java/com/sf/mapper/OrderItemMapper.java
  15. 29 0
      12306-demo/my-order-service/src/main/java/com/sf/mapper/OrderItemPassengerMapper.java
  16. 29 0
      12306-demo/my-order-service/src/main/java/com/sf/mapper/OrderMapper.java
  17. 8 0
      12306-demo/my-order-service/src/main/java/com/sf/service/IOrderItemService.java
  18. 8 0
      12306-demo/my-order-service/src/main/java/com/sf/service/IOrderPassengerRelationService.java
  19. 16 0
      12306-demo/my-order-service/src/main/java/com/sf/service/IOrderService.java
  20. 62 0
      12306-demo/my-order-service/src/main/java/com/sf/service/handler/DistributedIdGenerator.java
  21. 72 0
      12306-demo/my-order-service/src/main/java/com/sf/service/handler/OrderIdGeneratorManager.java
  22. 18 0
      12306-demo/my-order-service/src/main/java/com/sf/service/impl/OrderItemServiceImpl.java
  23. 18 0
      12306-demo/my-order-service/src/main/java/com/sf/service/impl/OrderPassengerRelationServiceImpl.java
  24. 80 0
      12306-demo/my-order-service/src/main/java/com/sf/service/impl/OrderServiceImpl.java
  25. 73 0
      12306-demo/my-order-service/src/main/java/com/sf/util/OrderCommonTableComplexAlgorithm.java
  26. 24 0
      12306-demo/my-order-service/src/main/resources/application.yml
  27. 77 0
      12306-demo/my-order-service/src/main/resources/shardingsphere-config.yml
  28. 13 0
      12306-demo/my-order-service/src/test/java/com/sf/myorderservice/MyOrderServiceApplicationTests.java
  29. 7 0
      12306-demo/my-ticket-service/pom.xml
  30. 2 0
      12306-demo/my-ticket-service/src/main/java/com/sf/TicketServiceApplication.java
  31. 61 0
      12306-demo/my-ticket-service/src/main/java/com/sf/config/Hippo4jThreadPoolConfiguration.java
  32. 9 0
      12306-demo/my-ticket-service/src/main/java/com/sf/controller/TicketController.java
  33. 54 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/domain/SelectSeatDTO.java
  34. 60 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/domain/TrainSeatBaseDTO.java
  35. 77 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/remote/PassengerRespDTO.java
  36. 97 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/remote/TicketOrderCreateRemoteReqDTO.java
  37. 79 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/remote/TicketOrderItemCreateRemoteReqDTO.java
  38. 38 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/req/PurchaseTicketPassengerDetailDTO.java
  39. 54 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/req/PurchaseTicketReqDTO.java
  40. 74 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/resp/TicketOrderDetailRespDTO.java
  41. 46 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/resp/TicketPurchaseRespDTO.java
  42. 78 0
      12306-demo/my-ticket-service/src/main/java/com/sf/dto/resp/TrainPurchaseTicketRespDTO.java
  43. 47 0
      12306-demo/my-ticket-service/src/main/java/com/sf/remote/TicketOrderRemoteService.java
  44. 41 0
      12306-demo/my-ticket-service/src/main/java/com/sf/remote/UserRemoteService.java
  45. 12 0
      12306-demo/my-ticket-service/src/main/java/com/sf/service/TicketService.java
  46. 496 0
      12306-demo/my-ticket-service/src/main/java/com/sf/service/handler/TrainBusinessClassPurchaseTicketHandler.java
  47. 180 0
      12306-demo/my-ticket-service/src/main/java/com/sf/service/handler/TrainSeatTypeSelector.java
  48. 98 2
      12306-demo/my-ticket-service/src/main/java/com/sf/service/impl/TicketServiceImpl.java
  49. 79 0
      12306-demo/my-ticket-service/src/main/java/com/sf/util/CarriageVacantSeatCalculateUtil.java
  50. 75 0
      12306-demo/my-ticket-service/src/main/java/com/sf/util/SeatNumberUtil.java
  51. 51 0
      12306-demo/my-ticket-service/src/main/java/com/sf/util/checkseat/BitMapCheckSeat.java
  52. 134 0
      12306-demo/my-ticket-service/src/main/java/com/sf/util/checkseat/SeatSelection.java
  53. 25 0
      12306-demo/my-ticket-service/src/main/java/com/sf/util/checkseat/TrainBitMapCheckSeat.java
  54. 104 0
      12306-demo/my-ticket-service/src/main/java/com/sf/util/checkseat/TrainBusinessCheckSeat.java
  55. 11 0
      12306-demo/my-user-service/src/main/java/com/sf/controller/PassengerController.java
  56. 79 0
      12306-demo/my-user-service/src/main/java/com/sf/dto/resp/PassengerActualRespDTO.java
  57. 11 0
      12306-demo/my-user-service/src/main/java/com/sf/service/IPassengerService.java
  58. 17 0
      12306-demo/my-user-service/src/main/java/com/sf/service/impl/PassengerServiceImpl.java

+ 33 - 0
12306-demo/my-order-service/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 18 - 0
12306-demo/my-order-service/.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1,18 @@
+# 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
+#
+#   https://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.
+wrapperVersion=3.3.1
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip

+ 250 - 0
12306-demo/my-order-service/mvnw

@@ -0,0 +1,250 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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
+#
+#    https://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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.1
+#
+# Optional ENV vars
+# -----------------
+#   JAVA_HOME - location of a JDK home dir, required when download maven via java source
+#   MVNW_REPOURL - repo url base for downloading maven distribution
+#   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+#   MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+  [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+  native_path() { cygpath --path --windows "$1"; }
+  ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+  # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+  if [ -n "${JAVA_HOME-}" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+      JAVACCMD="$JAVA_HOME/jre/sh/javac"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+      JAVACCMD="$JAVA_HOME/bin/javac"
+
+      if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+        echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+        echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+        return 1
+      fi
+    fi
+  else
+    JAVACMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v java
+    )" || :
+    JAVACCMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v javac
+    )" || :
+
+    if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+      echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+      return 1
+    fi
+  fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+  str="${1:-}" h=0
+  while [ -n "$str" ]; do
+    char="${str%"${str#?}"}"
+    h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+    str="${str#?}"
+  done
+  printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+  printf %s\\n "$1" >&2
+  exit 1
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+  case "${key-}" in
+  distributionUrl) distributionUrl="${value-}" ;;
+  distributionSha256Sum) distributionSha256Sum="${value-}" ;;
+  esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+  MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+  case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+  *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+  :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+  :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+  :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+  *)
+    echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+    distributionPlatform=linux-amd64
+    ;;
+  esac
+  distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+  ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+  unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+  exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+  verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+  clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+  trap clean HUP INT TERM EXIT
+else
+  die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+  distributionUrl="${distributionUrl%.zip}.tar.gz"
+  distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+  verbose "Found wget ... using wget"
+  wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+  verbose "Found curl ... using curl"
+  curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+  verbose "Falling back to use Java to download"
+  javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+  targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+  cat >"$javaSource" <<-END
+	public class Downloader extends java.net.Authenticator
+	{
+	  protected java.net.PasswordAuthentication getPasswordAuthentication()
+	  {
+	    return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+	  }
+	  public static void main( String[] args ) throws Exception
+	  {
+	    setDefault( new Downloader() );
+	    java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+	  }
+	}
+	END
+  # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+  verbose " - Compiling Downloader.java ..."
+  "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+  verbose " - Running Downloader.java ..."
+  "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+  distributionSha256Result=false
+  if [ "$MVN_CMD" = mvnd.sh ]; then
+    echo "Checksum validation is not supported for maven-mvnd." >&2
+    echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  elif command -v sha256sum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  elif command -v shasum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+    echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  fi
+  if [ $distributionSha256Result = false ]; then
+    echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+    echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+  unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+  tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"

+ 146 - 0
12306-demo/my-order-service/mvnw.cmd

@@ -0,0 +1,146 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.1
+@REM
+@REM Optional ENV vars
+@REM   MVNW_REPOURL - repo url base for downloading maven distribution
+@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+  IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+  $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+  Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+  "maven-mvnd-*" {
+    $USE_MVND = $true
+    $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+    $MVN_CMD = "mvnd.cmd"
+    break
+  }
+  default {
+    $USE_MVND = $false
+    $MVN_CMD = $script -replace '^mvnw','mvn'
+    break
+  }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+  $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+  $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+  Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+  exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+  Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+  if ($TMP_DOWNLOAD_DIR.Exists) {
+    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+    catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+  }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+  $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+  if ($USE_MVND) {
+    Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+  }
+  Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+  if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+    Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+  }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+  Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+  if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+    Write-Error "fail to move MAVEN_HOME"
+  }
+} finally {
+  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+  catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

+ 171 - 0
12306-demo/my-order-service/pom.xml

@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.sf</groupId>
+        <artifactId>demo-12306</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <groupId>com.sf</groupId>
+    <artifactId>my-order-service</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>my-order-service</name>
+    <description>my-order-service</description>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <java.version>17</java.version>
+        <mybatis-plus.version>3.5.3</mybatis-plus.version>
+        <springdoc.version>2.0.2</springdoc.version>
+        <shardingsphere.version>5.3.2</shardingsphere.version>
+        <lombok.version>1.18.32</lombok.version>
+        <index12306.version>0.0.1-SNAPSHOT</index12306.version>
+    </properties>
+
+    <dependencies>
+        <!-- springboot 的web依赖-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- mybatis plus 的依赖-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+
+        <!-- mysql驱动 的依赖-->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <!-- 整合jdbc -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+
+        <!-- lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+        </dependency>
+
+        <!-- 分库分表中间件 -->
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-jdbc-core</artifactId>
+            <version>${shardingsphere.version}</version>
+        </dependency>
+
+        <!-- swagger -->
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+            <version>${springdoc.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <version>${springdoc.version}</version>
+        </dependency>
+
+        <!-- 测试 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </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>
+
+        <!-- 微服务相关依赖 -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework.cloud</groupId>-->
+        <!--            <artifactId>spring-cloud-starter-bootstrap</artifactId>-->
+        <!--        </dependency>-->
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

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

@@ -0,0 +1,17 @@
+package com.sf;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@EnableDiscoveryClient
+@SpringBootApplication
+@MapperScan("com.sf.mapper")
+public class OrderServiceApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(OrderServiceApplication.class, args);
+    }
+
+}

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

@@ -0,0 +1,25 @@
+package com.sf.controller;
+
+import com.sf.dto.req.TicketOrderCreateReqDTO;
+import com.sf.service.IOrderService;
+import lombok.RequiredArgsConstructor;
+import org.opengoofy.index12306.framework.starter.convention.result.Result;
+import org.opengoofy.index12306.framework.starter.web.Results;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class OrderController {
+
+    private final IOrderService orderService;
+
+    /**
+     * 车票订单创建
+     */
+    @PostMapping("/api/order-service/order/ticket/create")
+    public Result<String> createTicketOrder(@RequestBody TicketOrderCreateReqDTO requestParam) {
+        return Results.success(orderService.createTicketOrder(requestParam));
+    }
+}

+ 91 - 0
12306-demo/my-order-service/src/main/java/com/sf/dto/req/TicketOrderCreateReqDTO.java

@@ -0,0 +1,91 @@
+/*
+ * 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 java.util.Date;
+import java.util.List;
+
+/**
+ * 车票订单创建请求参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+public class TicketOrderCreateReqDTO {
+
+    /**
+     * 用户 ID
+     */
+    private Long userId;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 车次 ID
+     */
+    private Long trainId;
+
+    /**
+     * 出发站点
+     */
+    private String departure;
+
+    /**
+     * 到达站点
+     */
+    private String arrival;
+
+    /**
+     * 订单来源
+     */
+    private Integer source;
+
+    /**
+     * 下单时间
+     */
+    private Date orderTime;
+
+    /**
+     * 乘车日期
+     */
+    private Date ridingDate;
+
+    /**
+     * 列车车次
+     */
+    private String trainNumber;
+
+    /**
+     * 出发时间
+     */
+    private Date departureTime;
+
+    /**
+     * 到达时间
+     */
+    private Date arrivalTime;
+
+    /**
+     * 订单明细
+     */
+    private List<TicketOrderItemCreateReqDTO> ticketOrderItems;
+}

+ 78 - 0
12306-demo/my-order-service/src/main/java/com/sf/dto/req/TicketOrderItemCreateReqDTO.java

@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+/**
+ * 车票订单详情创建请求参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+public class TicketOrderItemCreateReqDTO {
+
+    /**
+     * 车厢号
+     */
+    private String carriageNumber;
+
+    /**
+     * 座位类型
+     */
+    private Integer seatType;
+
+    /**
+     * 座位号
+     */
+    private String seatNumber;
+
+    /**
+     * 乘车人 ID
+     */
+    private String passengerId;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    /**
+     * 证件类型
+     */
+    private Integer idType;
+
+    /**
+     * 证件号
+     */
+    private String idCard;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 订单金额
+     */
+    private Integer amount;
+
+    /**
+     * 车票类型
+     */
+    private Integer ticketType;
+}

+ 119 - 0
12306-demo/my-order-service/src/main/java/com/sf/entity/OrderDO.java

@@ -0,0 +1,119 @@
+/*
+ * 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;
+
+import java.util.Date;
+
+/**
+ * 订单数据库实体
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@TableName("t_order")
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class OrderDO extends BaseDO {
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 订单号
+     */
+    private String orderSn;
+
+    /**
+     * 用户id
+     */
+    private String userId;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 列车id
+     */
+    private Long trainId;
+
+    /**
+     * 列车车次
+     */
+    private String trainNumber;
+
+    /**
+     * 出发站点
+     */
+    private String departure;
+
+    /**
+     * 到达站点
+     */
+    private String arrival;
+
+    /**
+     * 订单来源
+     */
+    private Integer source;
+
+    /**
+     * 订单状态
+     */
+    private Integer status;
+
+    /**
+     * 下单时间
+     */
+    private Date orderTime;
+
+    /**
+     * 支付方式
+     */
+    private Integer payType;
+
+    /**
+     * 支付时间
+     */
+    private Date payTime;
+
+    /**
+     * 乘车日期
+     */
+    private Date ridingDate;
+
+    /**
+     * 出发时间
+     */
+    private Date departureTime;
+
+    /**
+     * 出发时间
+     */
+    private Date arrivalTime;
+}

+ 112 - 0
12306-demo/my-order-service/src/main/java/com/sf/entity/OrderItemDO.java

@@ -0,0 +1,112 @@
+/*
+ * 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
+@TableName("t_order_item")
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class OrderItemDO extends BaseDO {
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 订单号
+     */
+    private String orderSn;
+
+    /**
+     * 用户id
+     */
+    private String userId;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 列车id
+     */
+    private Long trainId;
+
+    /**
+     * 车厢号
+     */
+    private String carriageNumber;
+
+    /**
+     * 座位类型
+     */
+    private Integer seatType;
+
+    /**
+     * 座位号
+     */
+    private String seatNumber;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    /**
+     * 证件类型
+     */
+    private Integer idType;
+
+    /**
+     * 证件号
+     */
+    private String idCard;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 订单状态
+     */
+    private Integer status;
+
+    /**
+     * 订单金额
+     */
+    private Integer amount;
+
+    /**
+     * 车票类型
+     */
+    private Integer ticketType;
+}

+ 57 - 0
12306-demo/my-order-service/src/main/java/com/sf/entity/OrderItemPassengerDO.java

@@ -0,0 +1,57 @@
+/*
+ * 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
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("t_order_item_passenger")
+public class OrderItemPassengerDO extends BaseDO {
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 订单号
+     */
+    private String orderSn;
+
+    /**
+     * 证件类型
+     */
+    private Integer idType;
+
+    /**
+     * 证件号
+     */
+    private String idCard;
+}

+ 62 - 0
12306-demo/my-order-service/src/main/java/com/sf/enums/OrderStatusEnum.java

@@ -0,0 +1,62 @@
+/*
+ * 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.enums;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 订单状态枚举
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Getter
+@RequiredArgsConstructor
+public enum OrderStatusEnum {
+
+    /**
+     * 待支付:用户选好车票下单,但还未付款的状态
+     */
+    PENDING_PAYMENT(0),
+
+    /**
+     * 已支付:用户支付订单费用
+     */
+    ALREADY_PAID(10),
+
+    /**
+     * 部分退款:用户支付订单费用后部分车票退款
+     */
+    PARTIAL_REFUND(11),
+
+    /**
+     * 全部退款:用户支付订单费用后全部车票退款
+     */
+    FULL_REFUND(12),
+
+    /**
+     * 已完成:用户车票已过上站时间,订单完成
+     */
+    COMPLETED(20),
+
+    /**
+     * 已取消:用户选好车票下单,未支付状态下取消订单
+     */
+    CLOSED(30);
+
+    private final int status;
+}

+ 28 - 0
12306-demo/my-order-service/src/main/java/com/sf/mapper/OrderItemMapper.java

@@ -0,0 +1,28 @@
+/*
+ * 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.OrderItemDO;
+
+/**
+ * 订单明细持久层
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public interface OrderItemMapper extends BaseMapper<OrderItemDO> {
+}

+ 29 - 0
12306-demo/my-order-service/src/main/java/com/sf/mapper/OrderItemPassengerMapper.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.OrderItemPassengerDO;
+
+
+/**
+ * 乘车人订单关系持久层
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public interface OrderItemPassengerMapper extends BaseMapper<OrderItemPassengerDO> {
+}

+ 29 - 0
12306-demo/my-order-service/src/main/java/com/sf/mapper/OrderMapper.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.OrderDO;
+
+
+/**
+ * 订单持久层
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public interface OrderMapper extends BaseMapper<OrderDO> {
+}

+ 8 - 0
12306-demo/my-order-service/src/main/java/com/sf/service/IOrderItemService.java

@@ -0,0 +1,8 @@
+package com.sf.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sf.entity.OrderItemDO;
+
+public interface IOrderItemService extends IService<OrderItemDO> {
+
+}

+ 8 - 0
12306-demo/my-order-service/src/main/java/com/sf/service/IOrderPassengerRelationService.java

@@ -0,0 +1,8 @@
+package com.sf.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sf.entity.OrderItemPassengerDO;
+
+public interface IOrderPassengerRelationService extends IService<OrderItemPassengerDO> {
+
+}

+ 16 - 0
12306-demo/my-order-service/src/main/java/com/sf/service/IOrderService.java

@@ -0,0 +1,16 @@
+package com.sf.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sf.dto.req.TicketOrderCreateReqDTO;
+import com.sf.entity.OrderDO;
+
+public interface IOrderService extends IService<OrderDO> {
+    /**
+     * 创建火车票订单
+     *
+     * @param requestParam 商品订单入参
+     * @return 订单号
+     */
+    String createTicketOrder(TicketOrderCreateReqDTO requestParam);
+
+}

+ 62 - 0
12306-demo/my-order-service/src/main/java/com/sf/service/handler/DistributedIdGenerator.java

@@ -0,0 +1,62 @@
+/*
+ * 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.service.handler;
+
+/**
+ * 全局唯一订单号生成器
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public class DistributedIdGenerator {
+
+    private static final long EPOCH = 1609459200000L;
+    private static final int NODE_BITS = 5;
+    private static final int SEQUENCE_BITS = 7;
+
+    private final long nodeID;
+    private long lastTimestamp = -1L;
+    private long sequence = 0L;
+
+    public DistributedIdGenerator(long nodeID) {
+        this.nodeID = nodeID;
+    }
+
+    public synchronized long generateId() {
+        long timestamp = System.currentTimeMillis() - EPOCH;
+        if (timestamp < lastTimestamp) {
+            throw new RuntimeException("Clock moved backwards. Refusing to generate ID.");
+        }
+        if (timestamp == lastTimestamp) {
+            sequence = (sequence + 1) & ((1 << SEQUENCE_BITS) - 1);
+            if (sequence == 0) {
+                timestamp = tilNextMillis(lastTimestamp);
+            }
+        } else {
+            sequence = 0L;
+        }
+        lastTimestamp = timestamp;
+        return (timestamp << (NODE_BITS + SEQUENCE_BITS)) | (nodeID << SEQUENCE_BITS) | sequence;
+    }
+
+    private long tilNextMillis(long lastTimestamp) {
+        long timestamp = System.currentTimeMillis() - EPOCH;
+        while (timestamp <= lastTimestamp) {
+            timestamp = System.currentTimeMillis() - EPOCH;
+        }
+        return timestamp;
+    }
+}

+ 72 - 0
12306-demo/my-order-service/src/main/java/com/sf/service/handler/OrderIdGeneratorManager.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.service.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.opengoofy.index12306.framework.starter.cache.DistributedCache;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+/**
+ * 订单 ID 全局唯一生成器管理
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Component
+@RequiredArgsConstructor
+public final class OrderIdGeneratorManager implements InitializingBean {
+
+    private final RedissonClient redissonClient;
+    private final DistributedCache distributedCache;
+    private static DistributedIdGenerator DISTRIBUTED_ID_GENERATOR;
+
+    /**
+     * 生成订单全局唯一 ID
+     *
+     * @param userId 用户名
+     * @return 订单 ID
+     */
+    public static String generateId(long userId) {
+        return DISTRIBUTED_ID_GENERATOR.generateId() + String.valueOf(userId % 1000000);
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        String LOCK_KEY = "distributed_id_generator_lock_key";
+        RLock lock = redissonClient.getLock(LOCK_KEY);
+        lock.lock();
+        try {
+            StringRedisTemplate instance = (StringRedisTemplate) distributedCache.getInstance();
+            String DISTRIBUTED_ID_GENERATOR_KEY = "distributed_id_generator_config";
+            long incremented = Optional.ofNullable(instance.opsForValue().increment(DISTRIBUTED_ID_GENERATOR_KEY)).orElse(0L);
+            // 注意:这里只是提供一种分库分表基因法的实现思路,所以将标识位定义 32。其次,如果对比 TB 网站订单号,应该不是在应用内生成,而是有一个全局服务调用获取
+            int NODE_MAX = 32;
+            if (incremented > NODE_MAX) {
+                incremented = 0;
+                instance.opsForValue().set(DISTRIBUTED_ID_GENERATOR_KEY, "0");
+            }
+            DISTRIBUTED_ID_GENERATOR = new DistributedIdGenerator(incremented);
+        } finally {
+            lock.unlock();
+        }
+    }
+}

+ 18 - 0
12306-demo/my-order-service/src/main/java/com/sf/service/impl/OrderItemServiceImpl.java

@@ -0,0 +1,18 @@
+
+package com.sf.service.impl;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sf.entity.OrderItemDO;
+import com.sf.mapper.OrderItemMapper;
+import com.sf.service.IOrderItemService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 订单明细接口层实现
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Service
+public class OrderItemServiceImpl extends ServiceImpl<OrderItemMapper, OrderItemDO> implements IOrderItemService {
+
+}

+ 18 - 0
12306-demo/my-order-service/src/main/java/com/sf/service/impl/OrderPassengerRelationServiceImpl.java

@@ -0,0 +1,18 @@
+
+package com.sf.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sf.entity.OrderItemPassengerDO;
+import com.sf.mapper.OrderItemPassengerMapper;
+import com.sf.service.IOrderPassengerRelationService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 乘车人订单关系接口层实现
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Service
+public class OrderPassengerRelationServiceImpl
+        extends ServiceImpl<OrderItemPassengerMapper, OrderItemPassengerDO>
+        implements IOrderPassengerRelationService {
+}

+ 80 - 0
12306-demo/my-order-service/src/main/java/com/sf/service/impl/OrderServiceImpl.java

@@ -0,0 +1,80 @@
+package com.sf.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sf.dto.req.TicketOrderCreateReqDTO;
+import com.sf.dto.req.TicketOrderItemCreateReqDTO;
+import com.sf.entity.OrderDO;
+import com.sf.entity.OrderItemDO;
+import com.sf.entity.OrderItemPassengerDO;
+import com.sf.enums.OrderStatusEnum;
+import com.sf.mapper.OrderMapper;
+import com.sf.service.IOrderItemService;
+import com.sf.service.IOrderPassengerRelationService;
+import com.sf.service.IOrderService;
+import com.sf.service.handler.OrderIdGeneratorManager;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderDO> implements IOrderService {
+
+    private final OrderMapper orderMapper;
+    private final IOrderItemService orderItemService;
+    private final IOrderPassengerRelationService orderPassengerRelationService;
+
+    @Override
+    public String createTicketOrder(TicketOrderCreateReqDTO requestParam) {
+        // 通过基因法将用户 ID 融入到订单号
+        String orderSn = OrderIdGeneratorManager.generateId(requestParam.getUserId());
+        OrderDO orderDO = OrderDO.builder().orderSn(orderSn)
+                .orderTime(requestParam.getOrderTime())
+                .departure(requestParam.getDeparture())
+                .departureTime(requestParam.getDepartureTime())
+                .ridingDate(requestParam.getRidingDate())
+                .arrivalTime(requestParam.getArrivalTime())
+                .trainNumber(requestParam.getTrainNumber())
+                .arrival(requestParam.getArrival())
+                .trainId(requestParam.getTrainId())
+                .source(requestParam.getSource())
+                .status(OrderStatusEnum.PENDING_PAYMENT.getStatus())
+                .username(requestParam.getUsername())
+                .userId(String.valueOf(requestParam.getUserId()))
+                .build();
+        orderMapper.insert(orderDO);
+        List<TicketOrderItemCreateReqDTO> ticketOrderItems = requestParam.getTicketOrderItems();
+        List<OrderItemDO> orderItemDOList = new ArrayList<>();
+        List<OrderItemPassengerDO> orderPassengerRelationDOList = new ArrayList<>();
+        ticketOrderItems.forEach(each -> {
+            OrderItemDO orderItemDO = OrderItemDO.builder()
+                    .trainId(requestParam.getTrainId())
+                    .seatNumber(each.getSeatNumber())
+                    .carriageNumber(each.getCarriageNumber())
+                    .realName(each.getRealName())
+                    .orderSn(orderSn)
+                    .phone(each.getPhone())
+                    .seatType(each.getSeatType())
+                    .username(requestParam.getUsername()).amount(each.getAmount()).carriageNumber(each.getCarriageNumber())
+                    .idCard(each.getIdCard())
+                    .ticketType(each.getTicketType())
+                    .idType(each.getIdType())
+                    .userId(String.valueOf(requestParam.getUserId()))
+                    .status(0)
+                    .build();
+            orderItemDOList.add(orderItemDO);
+            OrderItemPassengerDO orderPassengerRelationDO = OrderItemPassengerDO.builder()
+                    .idType(each.getIdType())
+                    .idCard(each.getIdCard())
+                    .orderSn(orderSn)
+                    .build();
+            orderPassengerRelationDOList.add(orderPassengerRelationDO);
+        });
+        orderItemService.saveBatch(orderItemDOList);
+        orderPassengerRelationService.saveBatch(orderPassengerRelationDOList);
+        // 发送 RocketMQ 延时消息,指定时间后取消订单
+        return orderSn;
+    }
+}

+ 73 - 0
12306-demo/my-order-service/src/main/java/com/sf/util/OrderCommonTableComplexAlgorithm.java

@@ -0,0 +1,73 @@
+package com.sf.util;
+
+import cn.hutool.core.collection.CollUtil;
+import com.google.common.base.Preconditions;
+import lombok.Getter;
+import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
+import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * 订单表相关复合分片算法配置
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public class OrderCommonTableComplexAlgorithm implements ComplexKeysShardingAlgorithm {
+
+    @Getter
+    private Properties props;
+
+    private int shardingCount;
+
+    private static final String SHARDING_COUNT_KEY = "sharding-count";
+
+    @Override
+    public Collection<String> doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValue) {
+        Map<String, Collection<Comparable<?>>> columnNameAndShardingValuesMap = shardingValue.getColumnNameAndShardingValuesMap();
+        Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
+        if (CollUtil.isNotEmpty(columnNameAndShardingValuesMap)) {
+            String userId = "user_id";
+            Collection<Comparable<?>> customerUserIdCollection = columnNameAndShardingValuesMap.get(userId);
+            if (CollUtil.isNotEmpty(customerUserIdCollection)) {
+                Comparable<?> comparable = customerUserIdCollection.stream().findFirst().get();
+                if (comparable instanceof String) {
+                    String actualUserId = comparable.toString();
+                    result.add(shardingValue.getLogicTableName() + "_" + hashShardingValue(actualUserId.substring(Math.max(actualUserId.length() - 6, 0))) % shardingCount);
+                } else {
+                    String dbSuffix = String.valueOf(hashShardingValue((Long) comparable % 1000000) % shardingCount);
+                    result.add(shardingValue.getLogicTableName() + "_" + dbSuffix);
+                }
+            } else {
+                String orderSn = "order_sn";
+                Collection<Comparable<?>> orderSnCollection = columnNameAndShardingValuesMap.get(orderSn);
+                Comparable<?> comparable = orderSnCollection.stream().findFirst().get();
+                if (comparable instanceof String) {
+                    String actualOrderSn = comparable.toString();
+                    result.add(shardingValue.getLogicTableName() + "_" + hashShardingValue(actualOrderSn.substring(Math.max(actualOrderSn.length() - 6, 0))) % shardingCount);
+                } else {
+                    String dbSuffix = String.valueOf(hashShardingValue((Long) comparable % 1000000) % shardingCount);
+                    result.add(shardingValue.getLogicTableName() + "_" + dbSuffix);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public void init(Properties props) {
+        this.props = props;
+        shardingCount = getShardingCount(props);
+    }
+
+    private int getShardingCount(final Properties props) {
+        Preconditions.checkArgument(props.containsKey(SHARDING_COUNT_KEY), "Sharding count cannot be null.");
+        return Integer.parseInt(props.getProperty(SHARDING_COUNT_KEY));
+    }
+
+    private long hashShardingValue(final Comparable<?> shardingValue) {
+        return Math.abs((long) shardingValue.hashCode());
+    }
+}

+ 24 - 0
12306-demo/my-order-service/src/main/resources/application.yml

@@ -0,0 +1,24 @@
+spring:
+  application:
+    name: my-order-service
+  datasource:
+    # 修改驱动
+    #    driver-class-name: com.mysql.cj.jdbc.Driver
+    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
+    #    username: root
+    #    password: root123456
+    #    url: jdbc:mysql://127.0.0.1:3306/railway_ticket?characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8
+    url: jdbc:shardingsphere:classpath:shardingsphere-config.yml
+  cloud:
+    nacos:
+      discovery:
+        server-addr: 127.0.0.1:8848
+      config:
+        server-addr: ${spring.cloud.nacos.discovery.server-addr}
+        import-check:
+          enabled: false
+      username: nacos
+      password: nacos
+
+server:
+  port: 9003

+ 77 - 0
12306-demo/my-order-service/src/main/resources/shardingsphere-config.yml

@@ -0,0 +1,77 @@
+# 数据源集合
+dataSources:
+  # 逻辑数据源名称
+  ds_0:
+    # 数据源类型
+    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
+    # 数据库驱动
+    driverClassName: com.mysql.cj.jdbc.Driver
+    # 数据库连接
+    jdbcUrl: jdbc:mysql://127.0.0.1:3306/railway?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
+    # 用户名,如果本地数据库与这个不一致,需要修改
+    username: root
+    # 密码,如果本地数据库与这个不一致,需要修改
+    password: root123456
+
+# ShardingSphere 规则配置,包含:数据分片、数据加密、读写分离等
+rules:
+  - !SHARDING
+    # 需要数据分片的表集合
+    tables:
+      # 逻辑表名
+      t_order:
+        actualDataNodes: ds_0.t_order_${0..15}
+        tableStrategy:
+          complex:
+            shardingColumns: user_id,order_sn
+            shardingAlgorithmName: order_table_complex_mod
+      t_order_item:
+        actualDataNodes: ds_0.t_order_item_${0..15}
+        tableStrategy:
+          complex:
+            shardingColumns: user_id,order_sn
+            shardingAlgorithmName: order_item_table_complex_mod
+      t_order_item_passenger:
+        actualDataNodes: ds_0.t_order_item_passenger_${0..15}
+        tableStrategy:
+          standard:
+            shardingColumn: id_card
+            shardingAlgorithmName: order_passenger_relation_table_mod
+    # 数据分片算法定义集合
+    shardingAlgorithms:
+      # 自定义分片算法名称
+      order_table_complex_mod:
+        type: CLASS_BASED
+        props:
+          algorithmClassName: com.sf.util.OrderCommonTableComplexAlgorithm
+          sharding-count: 16
+          strategy: complex
+      order_item_table_complex_mod:
+        type: CLASS_BASED
+        props:
+          algorithmClassName: com.sf.util.OrderCommonTableComplexAlgorithm
+          sharding-count: 16
+          strategy: complex
+      order_passenger_relation_table_mod:
+        type: HASH_MOD
+        props:
+          sharding-count: 16
+  - !ENCRYPT
+    tables:
+      t_order_item:
+        columns:
+          id_card:
+            cipherColumn: id_card
+            encryptorName: common_encryptor
+          phone:
+            cipherColumn: phone
+            encryptorName: common_encryptor
+        queryWithCipherColumn: true
+    encryptors:
+      common_encryptor:
+        type: AES
+        props:
+          aes-key-value: d6oadClrrb9A3GWo
+props:
+  # 是否打印逻辑SQL和真实SQL,开发测试环境建议开放,生产环境建议关闭
+  sql-show: true

+ 13 - 0
12306-demo/my-order-service/src/test/java/com/sf/myorderservice/MyOrderServiceApplicationTests.java

@@ -0,0 +1,13 @@
+package com.sf.myorderservice;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class MyOrderServiceApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}

+ 7 - 0
12306-demo/my-ticket-service/pom.xml

@@ -122,5 +122,12 @@
             <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.hippo4j</groupId>
+            <artifactId>hippo4j-config-spring-boot-starter</artifactId>
+            <version>1.5.0</version>
+        </dependency>
+
+
     </dependencies>
 </project>

+ 2 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/TicketServiceApplication.java

@@ -6,10 +6,12 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.web.servlet.ServletComponentScan;
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
 
 // 使用nacos 添加 @EnableDiscoveryClient
 // 使用自定义的starter  添加 @MyEnableAutoConfig
 @MyEnableAutoConfig
+@EnableFeignClients
 @EnableDiscoveryClient
 @SpringBootApplication
 @ServletComponentScan

+ 61 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/config/Hippo4jThreadPoolConfiguration.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.config;
+
+import cn.hippo4j.common.executor.support.BlockingQueueTypeEnum;
+import cn.hippo4j.core.executor.DynamicThreadPool;
+import cn.hippo4j.core.executor.support.ThreadPoolBuilder;
+import org.opengoofy.index12306.framework.starter.common.threadpool.proxy.RejectedProxyUtil;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Hippo4j 动态线程池配置
+ * <a href="https://github.com/opengoofy/hippo4j">异步线程池框架,支持线程池动态变更&监控&报警</a>
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Configuration
+public class Hippo4jThreadPoolConfiguration {
+
+    /**
+     * 分配一个用户购买不同类型车票的线程池
+     * <a href="https://nageoffer.com/12306/question">线程池参数如何设置</>
+     */
+    @Bean
+    @DynamicThreadPool
+    public ThreadPoolExecutor selectSeatThreadPoolExecutor() {
+        AtomicLong atomicLong = new AtomicLong();
+        String threadPoolId = "select-seat-thread-pool-executor";
+        return ThreadPoolBuilder.builder()
+                .threadPoolId(threadPoolId)
+                .threadFactory(threadPoolId)
+                .workQueue(BlockingQueueTypeEnum.SYNCHRONOUS_QUEUE)
+                .corePoolSize(24)
+                .maximumPoolSize(36)
+                .allowCoreThreadTimeOut(true)
+                .keepAliveTime(60, TimeUnit.MINUTES)
+//                .rejected(new ThreadPoolExecutor.CallerRunsPolicy())
+                .rejected(RejectedProxyUtil.createProxy(new ThreadPoolExecutor.CallerRunsPolicy(),atomicLong))
+                .dynamicPool()
+                .build();
+    }
+}

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

@@ -1,12 +1,16 @@
 package com.sf.controller;
 
+import com.sf.dto.req.PurchaseTicketReqDTO;
 import com.sf.dto.req.TicketPageQueryReqDTO;
 import com.sf.dto.resp.TicketPageQueryRespDTO;
+import com.sf.dto.resp.TicketPurchaseRespDTO;
 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.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
@@ -22,4 +26,9 @@ public class TicketController {
         return Results.success(ticketService.pageListTicketQueryByMysql(requestParam));
     }
 
+    @PostMapping("/api/ticket-service/ticket/purchase/v2")
+    public Result<TicketPurchaseRespDTO> purchaseTicketsV2(@RequestBody PurchaseTicketReqDTO requestParam) {
+        return Results.success(ticketService.purchaseTicketsV2(requestParam));
+    }
+
 }

+ 54 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/domain/SelectSeatDTO.java

@@ -0,0 +1,54 @@
+/*
+ * 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.domain;
+
+import com.sf.dto.req.PurchaseTicketPassengerDetailDTO;
+import com.sf.dto.req.PurchaseTicketReqDTO;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+import java.util.List;
+
+/**
+ * 选择座位实体
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public final class SelectSeatDTO {
+
+    /**
+     * 座位类型
+     */
+    private Integer seatType;
+
+    /**
+     * 座位对应的乘车人集合
+     */
+    private List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails;
+
+    /**
+     * 购票原始入参
+     */
+    private PurchaseTicketReqDTO requestParam;
+}

+ 60 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/domain/TrainSeatBaseDTO.java

@@ -0,0 +1,60 @@
+/*
+ * 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.domain;
+
+import com.sf.dto.req.PurchaseTicketPassengerDetailDTO;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 高铁座位基础信息
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Builder
+@Data
+@AllArgsConstructor
+public class TrainSeatBaseDTO {
+
+    /**
+     * 高铁列车 ID
+     */
+    private String trainId;
+
+    /**
+     * 列车起始站点
+     */
+    private String departure;
+
+    /**
+     * 列车到达站点
+     */
+    private String arrival;
+
+    /**
+     * 乘客信息
+     */
+    private List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails;
+
+    /**
+     * 选择座位信息
+     */
+    private List<String> chooseSeatList;
+}

+ 77 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/remote/PassengerRespDTO.java

@@ -0,0 +1,77 @@
+/*
+ * 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.remote;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 乘车人返回参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+public class PassengerRespDTO {
+
+    /**
+     * 乘车人id
+     */
+    private String id;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    /**
+     * 证件类型
+     */
+    private Integer idType;
+
+    /**
+     * 证件号码
+     */
+    private String idCard;
+
+    /**
+     * 优惠类型,同用户类型 儿童、成人、学生、残疾军人
+     */
+    private Integer discountType;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 添加日期
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date createDate;
+
+    /**
+     * 审核状态
+     */
+    private Integer verifyStatus;
+}

+ 97 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/remote/TicketOrderCreateRemoteReqDTO.java

@@ -0,0 +1,97 @@
+/*
+ * 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.remote;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 车票订单创建请求参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TicketOrderCreateRemoteReqDTO {
+
+    /**
+     * 用户 ID
+     */
+    private String userId;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 车次 ID
+     */
+    private Long trainId;
+
+    /**
+     * 出发站点
+     */
+    private String departure;
+
+    /**
+     * 到达站点
+     */
+    private String arrival;
+
+    /**
+     * 订单来源
+     */
+    private Integer source;
+
+    /**
+     * 下单时间
+     */
+    private Date orderTime;
+
+    /**
+     * 乘车日期
+     */
+    private Date ridingDate;
+
+    /**
+     * 列车车次
+     */
+    private String trainNumber;
+
+    /**
+     * 出发时间
+     */
+    private Date departureTime;
+
+    /**
+     * 到达时间
+     */
+    private Date arrivalTime;
+
+    /**
+     * 订单明细
+     */
+    private List<TicketOrderItemCreateRemoteReqDTO> ticketOrderItems;
+}

+ 79 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/remote/TicketOrderItemCreateRemoteReqDTO.java

@@ -0,0 +1,79 @@
+/*
+ * 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.remote;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 车票订单详情创建请求参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TicketOrderItemCreateRemoteReqDTO {
+
+    /**
+     * 车厢号
+     */
+    private String carriageNumber;
+
+    /**
+     * 座位类型
+     */
+    private Integer seatType;
+
+    /**
+     * 座位号
+     */
+    private String seatNumber;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    /**
+     * 证件类型
+     */
+    private Integer idType;
+
+    /**
+     * 证件号
+     */
+    private String idCard;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 订单金额
+     */
+    private Integer amount;
+
+    /**
+     * 车票类型
+     */
+    private Integer ticketType;
+}

+ 38 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/req/PurchaseTicketPassengerDetailDTO.java

@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * 购票乘车人详情实体
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+public class PurchaseTicketPassengerDetailDTO {
+
+    /**
+     * 乘车人 ID
+     */
+    private String passengerId;
+
+    /**
+     * 座位类型
+     */
+    private Integer seatType;
+}

+ 54 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/req/PurchaseTicketReqDTO.java

@@ -0,0 +1,54 @@
+/*
+ * 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 java.util.List;
+
+/**
+ * 购票请求入参
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+public class PurchaseTicketReqDTO {
+
+    /**
+     * 车次 ID
+     */
+    private String trainId;
+
+    /**
+     * 乘车人
+     */
+    private List<PurchaseTicketPassengerDetailDTO> passengers;
+
+    /**
+     * 选择座位
+     */
+    private List<String> chooseSeats;
+
+    /**
+     * 出发站点
+     */
+    private String departure;
+
+    /**
+     * 到达站点
+     */
+    private String arrival;
+}

+ 74 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/resp/TicketOrderDetailRespDTO.java

@@ -0,0 +1,74 @@
+/*
+ * 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 lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 车票订单详情返回参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TicketOrderDetailRespDTO {
+
+    /**
+     * 席别类型
+     */
+    private Integer seatType;
+
+    /**
+     * 车厢号
+     */
+    private String carriageNumber;
+
+    /**
+     * 座位号
+     */
+    private String seatNumber;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    /**
+     * 证件类型
+     */
+    private Integer idType;
+
+    /**
+     * 证件号
+     */
+    private String idCard;
+
+    /**
+     * 车票类型 0:成人 1:儿童 2:学生 3:残疾军人
+     */
+    private Integer ticketType;
+
+    /**
+     * 订单金额
+     */
+    private Integer amount;
+}

+ 46 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/resp/TicketPurchaseRespDTO.java

@@ -0,0 +1,46 @@
+/*
+ * 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 lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 车票购买返回参数
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class TicketPurchaseRespDTO {
+
+    /**
+     * 订单号
+     */
+    private String orderSn;
+
+    /**
+     * 乘车人订单详情
+     */
+    private List<TicketOrderDetailRespDTO> ticketOrderDetails;
+}

+ 78 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/dto/resp/TrainPurchaseTicketRespDTO.java

@@ -0,0 +1,78 @@
+/*
+ * 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 lombok.Data;
+
+/**
+ * 列车购票出参
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+public class TrainPurchaseTicketRespDTO {
+
+    /**
+     * 乘车人 ID
+     */
+    private String passengerId;
+
+    /**
+     * 乘车人姓名
+     */
+    private String realName;
+
+    /**
+     * 乘车人证件类型
+     */
+    private Integer idType;
+
+    /**
+     * 乘车人证件号
+     */
+    private String idCard;
+
+    /**
+     * 乘车人手机号
+     */
+    private String phone;
+
+    /**
+     * 用户类型 0:成人 1:儿童 2:学生 3:残疾军人
+     */
+    private Integer userType;
+
+    /**
+     * 席别类型
+     */
+    private Integer seatType;
+
+    /**
+     * 车厢号
+     */
+    private String carriageNumber;
+
+    /**
+     * 座位号
+     */
+    private String seatNumber;
+
+    /**
+     * 座位金额
+     */
+    private Integer amount;
+}

+ 47 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/remote/TicketOrderRemoteService.java

@@ -0,0 +1,47 @@
+/*
+ * 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.remote;
+
+import com.sf.dto.remote.TicketOrderCreateRemoteReqDTO;
+import org.opengoofy.index12306.framework.starter.convention.result.Result;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.cloud.openfeign.SpringQueryMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+
+/**
+ * 车票订单远程服务调用
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@FeignClient("my-order-service")
+public interface TicketOrderRemoteService {
+
+    /**
+     * 创建车票订单
+     *
+     * @param requestParam 创建车票订单请求参数
+     * @return 订单号
+     */
+    @PostMapping("/api/order-service/order/ticket/create")
+    Result<String> createTicketOrder(@RequestBody TicketOrderCreateRemoteReqDTO requestParam);
+
+}

+ 41 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/remote/UserRemoteService.java

@@ -0,0 +1,41 @@
+/*
+ * 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.remote;
+
+import com.sf.dto.remote.PassengerRespDTO;
+import org.opengoofy.index12306.framework.starter.convention.result.Result;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+
+/**
+ * 用户远程服务调用
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@FeignClient("my-user-service")
+public interface UserRemoteService {
+
+    /**
+     * 根据乘车人 ID 集合查询乘车人列表
+     */
+    @GetMapping("/api/user-service/inner/passenger/actual/query/ids")
+    Result<List<PassengerRespDTO>> listPassengerQueryByIds(
+            @RequestParam("username") String username, @RequestParam("ids") List<String> ids);
+}

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

@@ -1,9 +1,12 @@
 package com.sf.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.sf.dto.req.PurchaseTicketReqDTO;
 import com.sf.dto.req.TicketPageQueryReqDTO;
 import com.sf.dto.resp.TicketPageQueryRespDTO;
+import com.sf.dto.resp.TicketPurchaseRespDTO;
 import com.sf.entity.TicketDO;
+import org.springframework.web.bind.annotation.RequestBody;
 
 public interface TicketService extends IService<TicketDO> {
 
@@ -16,4 +19,13 @@ public interface TicketService extends IService<TicketDO> {
      * @return 查询车票返回结果
      */
     TicketPageQueryRespDTO pageListTicketQueryByRedis(TicketPageQueryReqDTO requestParam);
+
+    /**
+     * 购买车票V2高性能版本
+     *
+     * @param requestParam 车票购买请求参数
+     * @return 订单号
+     */
+    TicketPurchaseRespDTO purchaseTicketsV2(@RequestBody PurchaseTicketReqDTO requestParam);
+
 }

+ 496 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/service/handler/TrainBusinessClassPurchaseTicketHandler.java

@@ -0,0 +1,496 @@
+package com.sf.service.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.lang.Pair;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.google.common.collect.Lists;
+import com.sf.dto.domain.SelectSeatDTO;
+import com.sf.dto.domain.TrainSeatBaseDTO;
+import com.sf.dto.req.PurchaseTicketPassengerDetailDTO;
+import com.sf.dto.resp.TrainPurchaseTicketRespDTO;
+import com.sf.entity.SeatDO;
+import com.sf.mapper.SeatMapper;
+import com.sf.util.CarriageVacantSeatCalculateUtil;
+import com.sf.util.SeatNumberUtil;
+import com.sf.util.checkseat.BitMapCheckSeat;
+import com.sf.util.checkseat.SeatSelection;
+import com.sf.util.checkseat.TrainBusinessCheckSeat;
+import lombok.RequiredArgsConstructor;
+import org.opengoofy.index12306.framework.starter.convention.exception.ServiceException;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import static com.sf.constant.RedisKeyConstant.TRAIN_STATION_CARRIAGE_REMAINING_TICKET;
+
+/**
+ * 高铁商务座购票组件
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Component
+@RequiredArgsConstructor
+public class TrainBusinessClassPurchaseTicketHandler  {
+
+    // seatService -> seatMapper
+    private final SeatMapper seatMapper;
+
+    private static final Map<Character, Integer> SEAT_Y_INT = Map.of('A', 0, 'C', 1, 'F', 2);
+
+    public List<TrainPurchaseTicketRespDTO> selectSeats(SelectSeatDTO requestParam) {
+        String trainId = requestParam.getRequestParam().getTrainId();
+        String departure = requestParam.getRequestParam().getDeparture();
+        String arrival = requestParam.getRequestParam().getArrival();
+        List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails = requestParam.getPassengerSeatDetails();
+        List<String> trainCarriageList = listUsableCarriageNumber(trainId, requestParam.getSeatType(), departure, arrival);
+        List<Integer> trainStationCarriageRemainingTicket = listSeatRemainingTicket(trainId, departure, arrival, trainCarriageList);
+        int remainingTicketSum = trainStationCarriageRemainingTicket.stream().mapToInt(Integer::intValue).sum();
+        if (remainingTicketSum < passengerSeatDetails.size()) {
+            throw new ServiceException("站点余票不足,请尝试更换座位类型或选择其它站点");
+        }
+        if (passengerSeatDetails.size() < 3) {
+            if (CollUtil.isNotEmpty(requestParam.getRequestParam().getChooseSeats())) {
+                Pair<List<TrainPurchaseTicketRespDTO>, Boolean> actualSeatPair = findMatchSeats(requestParam, trainCarriageList, trainStationCarriageRemainingTicket);
+                return actualSeatPair.getKey();
+            }
+            return selectSeats(requestParam, trainCarriageList, trainStationCarriageRemainingTicket);
+        } else {
+            if (CollUtil.isNotEmpty(requestParam.getRequestParam().getChooseSeats())) {
+                Pair<List<TrainPurchaseTicketRespDTO>, Boolean> actualSeatPair = findMatchSeats(requestParam, trainCarriageList, trainStationCarriageRemainingTicket);
+                return actualSeatPair.getKey();
+            }
+            return selectComplexSeats(requestParam, trainCarriageList, trainStationCarriageRemainingTicket);
+        }
+    }
+
+    public List<String> listUsableCarriageNumber(String trainId, Integer carriageType, String departure, String arrival) {
+        LambdaQueryWrapper<SeatDO> queryWrapper = Wrappers.lambdaQuery(SeatDO.class)
+                .eq(SeatDO::getTrainId, trainId)
+                .eq(SeatDO::getSeatType, carriageType)
+                .eq(SeatDO::getStartStation, departure)
+                .eq(SeatDO::getEndStation, arrival)
+                .eq(SeatDO::getSeatStatus, 0) // 座位可售状态
+                .groupBy(SeatDO::getCarriageNumber)
+                .select(SeatDO::getCarriageNumber);
+        List<SeatDO> seatDOList = seatMapper.selectList(queryWrapper);
+        return seatDOList.stream().map(SeatDO::getCarriageNumber).collect(Collectors.toList());
+    }
+
+    public List<Integer> listSeatRemainingTicket(String trainId, String departure, String arrival, List<String> trainCarriageList) {
+        SeatDO seatDO = SeatDO.builder()
+                .trainId(Long.parseLong(trainId))
+                .startStation(departure)
+                .endStation(arrival)
+                .build();
+        return seatMapper.listSeatRemainingTicket(seatDO, trainCarriageList);
+    }
+
+    private Pair<List<TrainPurchaseTicketRespDTO>, Boolean> findMatchSeats(SelectSeatDTO requestParam, List<String> trainCarriageList, List<Integer> trainStationCarriageRemainingTicket) {
+        TrainSeatBaseDTO trainSeatBaseDTO = buildTrainSeatBaseDTO(requestParam);
+        int chooseSeatSize = trainSeatBaseDTO.getChooseSeatList().size();
+        List<TrainPurchaseTicketRespDTO> actualResult = Lists.newArrayListWithCapacity(trainSeatBaseDTO.getPassengerSeatDetails().size());
+        BitMapCheckSeat instance = new TrainBusinessCheckSeat();
+        HashMap<String, List<Pair<Integer, Integer>>> carriagesSeatMap = new HashMap<>(4);
+        int passengersNumber = trainSeatBaseDTO.getPassengerSeatDetails().size();
+        for (int i = 0; i < trainStationCarriageRemainingTicket.size(); i++) {
+            String carriagesNumber = trainCarriageList.get(i);
+            List<String> listAvailableSeat = listAvailableSeat(trainSeatBaseDTO.getTrainId(), carriagesNumber, requestParam.getSeatType(), trainSeatBaseDTO.getDeparture(), trainSeatBaseDTO.getArrival());
+            int[][] actualSeats = new int[2][3];
+            for (int j = 1; j < 3; j++) {
+                for (int k = 1; k < 4; k++) {
+                    actualSeats[j - 1][k - 1] = listAvailableSeat.contains("0" + j + SeatNumberUtil.convert(0, k)) ? 0 : 1;
+                }
+            }
+            List<Pair<Integer, Integer>> vacantSeatList = CarriageVacantSeatCalculateUtil.buildCarriageVacantSeatList2(actualSeats, 2, 3);
+            boolean isExists = instance.checkChooseSeat(trainSeatBaseDTO.getChooseSeatList(), actualSeats, SEAT_Y_INT);
+            long vacantSeatCount = vacantSeatList.size();
+            List<Pair<Integer, Integer>> sureSeatList = new ArrayList<>();
+            List<String> selectSeats = Lists.newArrayListWithCapacity(passengersNumber);
+            boolean flag = false;
+            if (isExists && vacantSeatCount >= passengersNumber) {
+                Iterator<Pair<Integer, Integer>> pairIterator = vacantSeatList.iterator();
+                for (int i1 = 0; i1 < chooseSeatSize; i1++) {
+                    if (chooseSeatSize == 1) {
+                        String chooseSeat = trainSeatBaseDTO.getChooseSeatList().get(i1);
+                        int seatX = Integer.parseInt(chooseSeat.substring(1));
+                        int seatY = SEAT_Y_INT.get(chooseSeat.charAt(0));
+                        if (actualSeats[seatX][seatY] == 0) {
+                            sureSeatList.add(new Pair<>(seatX, seatY));
+                            while (pairIterator.hasNext()) {
+                                Pair<Integer, Integer> pair = pairIterator.next();
+                                if (pair.getKey() == seatX && pair.getValue() == seatY) {
+                                    pairIterator.remove();
+                                    break;
+                                }
+                            }
+                        } else {
+                            if (actualSeats[1][seatY] == 0) {
+                                sureSeatList.add(new Pair<>(1, seatY));
+                                while (pairIterator.hasNext()) {
+                                    Pair<Integer, Integer> pair = pairIterator.next();
+                                    if (pair.getKey() == 1 && pair.getValue() == seatY) {
+                                        pairIterator.remove();
+                                        break;
+                                    }
+                                }
+                            } else {
+                                flag = true;
+                            }
+                        }
+                    } else {
+                        String chooseSeat = trainSeatBaseDTO.getChooseSeatList().get(i1);
+                        int seatX = Integer.parseInt(chooseSeat.substring(1));
+                        int seatY = SEAT_Y_INT.get(chooseSeat.charAt(0));
+                        if (actualSeats[seatX][seatY] == 0) {
+                            sureSeatList.add(new Pair<>(seatX, seatY));
+                            while (pairIterator.hasNext()) {
+                                Pair<Integer, Integer> pair = pairIterator.next();
+                                if (pair.getKey() == seatX && pair.getValue() == seatY) {
+                                    pairIterator.remove();
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+                if (flag && i < trainStationCarriageRemainingTicket.size() - 1) {
+                    continue;
+                }
+                if (sureSeatList.size() != passengersNumber) {
+                    int needSeatSize = passengersNumber - sureSeatList.size();
+                    sureSeatList.addAll(vacantSeatList.subList(0, needSeatSize));
+                }
+                for (Pair<Integer, Integer> each : sureSeatList) {
+                    selectSeats.add("0" + (each.getKey() + 1) + SeatNumberUtil.convert(0, (each.getValue() + 1)));
+                }
+                AtomicInteger countNum = new AtomicInteger(0);
+                for (String selectSeat : selectSeats) {
+                    TrainPurchaseTicketRespDTO result = new TrainPurchaseTicketRespDTO();
+                    PurchaseTicketPassengerDetailDTO currentTicketPassenger = trainSeatBaseDTO.getPassengerSeatDetails().get(countNum.getAndIncrement());
+                    result.setSeatNumber(selectSeat);
+                    result.setSeatType(currentTicketPassenger.getSeatType());
+                    result.setCarriageNumber(carriagesNumber);
+                    result.setPassengerId(currentTicketPassenger.getPassengerId());
+                    actualResult.add(result);
+                }
+                return new Pair<>(actualResult, Boolean.TRUE);
+            } else {
+                if (i < trainStationCarriageRemainingTicket.size()) {
+                    if (vacantSeatCount > 0) {
+                        carriagesSeatMap.put(carriagesNumber, vacantSeatList);
+                    }
+                    if (i == trainStationCarriageRemainingTicket.size() - 1) {
+                        Pair<String, List<Pair<Integer, Integer>>> findSureCarriage = null;
+                        for (Map.Entry<String, List<Pair<Integer, Integer>>> entry : carriagesSeatMap.entrySet()) {
+                            if (entry.getValue().size() >= passengersNumber) {
+                                findSureCarriage = new Pair<>(entry.getKey(), entry.getValue().subList(0, passengersNumber));
+                                break;
+                            }
+                        }
+                        if (null != findSureCarriage) {
+                            sureSeatList = findSureCarriage.getValue().subList(0, passengersNumber);
+                            for (Pair<Integer, Integer> each : sureSeatList) {
+                                selectSeats.add("0" + (each.getKey() + 1) + SeatNumberUtil.convert(0, each.getValue() + 1));
+                            }
+                            AtomicInteger countNum = new AtomicInteger(0);
+                            for (String selectSeat : selectSeats) {
+                                TrainPurchaseTicketRespDTO result = new TrainPurchaseTicketRespDTO();
+                                PurchaseTicketPassengerDetailDTO currentTicketPassenger = trainSeatBaseDTO.getPassengerSeatDetails().get(countNum.getAndIncrement());
+                                result.setSeatNumber(selectSeat);
+                                result.setSeatType(currentTicketPassenger.getSeatType());
+                                result.setCarriageNumber(findSureCarriage.getKey());
+                                result.setPassengerId(currentTicketPassenger.getPassengerId());
+                                actualResult.add(result);
+                            }
+                        } else {
+                            int sureSeatListSize = 0;
+                            AtomicInteger countNum = new AtomicInteger(0);
+                            for (Map.Entry<String, List<Pair<Integer, Integer>>> entry : carriagesSeatMap.entrySet()) {
+                                if (sureSeatListSize < passengersNumber) {
+                                    if (sureSeatListSize + entry.getValue().size() < passengersNumber) {
+                                        sureSeatListSize = sureSeatListSize + entry.getValue().size();
+                                        List<String> actualSelectSeats = new ArrayList<>();
+                                        for (Pair<Integer, Integer> each : entry.getValue()) {
+                                            actualSelectSeats.add("0" + (each.getKey() + 1) + SeatNumberUtil.convert(0, each.getValue() + 1));
+                                        }
+                                        for (String selectSeat : actualSelectSeats) {
+                                            TrainPurchaseTicketRespDTO result = new TrainPurchaseTicketRespDTO();
+                                            PurchaseTicketPassengerDetailDTO currentTicketPassenger = trainSeatBaseDTO.getPassengerSeatDetails().get(countNum.getAndIncrement());
+                                            result.setSeatNumber(selectSeat);
+                                            result.setSeatType(currentTicketPassenger.getSeatType());
+                                            result.setCarriageNumber(entry.getKey());
+                                            result.setPassengerId(currentTicketPassenger.getPassengerId());
+                                            actualResult.add(result);
+                                        }
+                                    } else {
+                                        int needSeatSize = entry.getValue().size() - (sureSeatListSize + entry.getValue().size() - passengersNumber);
+                                        sureSeatListSize = sureSeatListSize + needSeatSize;
+                                        if (sureSeatListSize >= passengersNumber) {
+                                            List<String> actualSelectSeats = new ArrayList<>();
+                                            for (Pair<Integer, Integer> each : entry.getValue().subList(0, needSeatSize)) {
+                                                actualSelectSeats.add("0" + (each.getKey() + 1) + SeatNumberUtil.convert(0, each.getValue() + 1));
+                                            }
+                                            for (String selectSeat : actualSelectSeats) {
+                                                TrainPurchaseTicketRespDTO result = new TrainPurchaseTicketRespDTO();
+                                                PurchaseTicketPassengerDetailDTO currentTicketPassenger = trainSeatBaseDTO.getPassengerSeatDetails().get(countNum.getAndIncrement());
+                                                result.setSeatNumber(selectSeat);
+                                                result.setSeatType(currentTicketPassenger.getSeatType());
+                                                result.setCarriageNumber(entry.getKey());
+                                                result.setPassengerId(currentTicketPassenger.getPassengerId());
+                                                actualResult.add(result);
+                                            }
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        return new Pair<>(actualResult, Boolean.TRUE);
+                    }
+                }
+            }
+        }
+        return new Pair<>(null, Boolean.FALSE);
+    }
+
+    private List<TrainPurchaseTicketRespDTO> selectSeats(SelectSeatDTO requestParam, List<String> trainCarriageList, List<Integer> trainStationCarriageRemainingTicket) {
+        String trainId = requestParam.getRequestParam().getTrainId();
+        String departure = requestParam.getRequestParam().getDeparture();
+        String arrival = requestParam.getRequestParam().getArrival();
+        List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails = requestParam.getPassengerSeatDetails();
+        List<TrainPurchaseTicketRespDTO> actualResult = new ArrayList<>();
+        Map<String, Integer> demotionStockNumMap = new LinkedHashMap<>();
+        Map<String, int[][]> actualSeatsMap = new HashMap<>();
+        Map<String, int[][]> carriagesNumberSeatsMap = new HashMap<>();
+        String carriagesNumber;
+        for (int i = 0; i < trainStationCarriageRemainingTicket.size(); i++) {
+            carriagesNumber = trainCarriageList.get(i);
+            List<String> listAvailableSeat = listAvailableSeat(trainId, carriagesNumber, requestParam.getSeatType(), departure, arrival);
+            int[][] actualSeats = new int[2][3];
+            for (int j = 1; j < 3; j++) {
+                for (int k = 1; k < 4; k++) {
+                    // 当前默认按照复兴号商务座排序,后续这里需要按照简单工厂对车类型进行获取 y 轴
+                    actualSeats[j - 1][k - 1] = listAvailableSeat.contains("0" + j + SeatNumberUtil.convert(0, k)) ? 0 : 1;
+                }
+            }
+            int[][] select = SeatSelection.adjacent(passengerSeatDetails.size(), actualSeats);
+            if (select != null) {
+                carriagesNumberSeatsMap.put(carriagesNumber, select);
+                break;
+            }
+            int demotionStockNum = 0;
+            for (int[] actualSeat : actualSeats) {
+                for (int i1 : actualSeat) {
+                    if (i1 == 0) {
+                        demotionStockNum++;
+                    }
+                }
+            }
+            demotionStockNumMap.putIfAbsent(carriagesNumber, demotionStockNum);
+            actualSeatsMap.putIfAbsent(carriagesNumber, actualSeats);
+            if (i < trainStationCarriageRemainingTicket.size() - 1) {
+                continue;
+            }
+            // 如果邻座算法无法匹配,尝试对用户进行降级分配:同车厢不邻座
+            for (Map.Entry<String, Integer> entry : demotionStockNumMap.entrySet()) {
+                String carriagesNumberBack = entry.getKey();
+                int demotionStockNumBack = entry.getValue();
+                if (demotionStockNumBack > passengerSeatDetails.size()) {
+                    int[][] seats = actualSeatsMap.get(carriagesNumberBack);
+                    int[][] nonAdjacentSeats = SeatSelection.nonAdjacent(passengerSeatDetails.size(), seats);
+                    if (Objects.equals(nonAdjacentSeats.length, passengerSeatDetails.size())) {
+                        select = nonAdjacentSeats;
+                        carriagesNumberSeatsMap.put(carriagesNumberBack, select);
+                        break;
+                    }
+                }
+            }
+            // 如果同车厢也已无法匹配,则对用户座位再次降级:不同车厢不邻座
+            if (Objects.isNull(select)) {
+                for (Map.Entry<String, Integer> entry : demotionStockNumMap.entrySet()) {
+                    String carriagesNumberBack = entry.getKey();
+                    int demotionStockNumBack = entry.getValue();
+                    int[][] seats = actualSeatsMap.get(carriagesNumberBack);
+                    int[][] nonAdjacentSeats = SeatSelection.nonAdjacent(demotionStockNumBack, seats);
+                    carriagesNumberSeatsMap.put(entry.getKey(), nonAdjacentSeats);
+                }
+            }
+        }
+        // 乘车人员在单一车厢座位不满足,触发乘车人元分布在不同车厢
+        int count = (int) carriagesNumberSeatsMap.values().stream()
+                .flatMap(Arrays::stream)
+                .count();
+        if (CollUtil.isNotEmpty(carriagesNumberSeatsMap) && passengerSeatDetails.size() == count) {
+            int countNum = 0;
+            for (Map.Entry<String, int[][]> entry : carriagesNumberSeatsMap.entrySet()) {
+                List<String> selectSeats = new ArrayList<>();
+                for (int[] ints : entry.getValue()) {
+                    selectSeats.add("0" + ints[0] + SeatNumberUtil.convert(0, ints[1]));
+                }
+                for (String selectSeat : selectSeats) {
+                    TrainPurchaseTicketRespDTO result = new TrainPurchaseTicketRespDTO();
+                    PurchaseTicketPassengerDetailDTO currentTicketPassenger = passengerSeatDetails.get(countNum++);
+                    result.setSeatNumber(selectSeat);
+                    result.setSeatType(currentTicketPassenger.getSeatType());
+                    result.setCarriageNumber(entry.getKey());
+                    result.setPassengerId(currentTicketPassenger.getPassengerId());
+                    actualResult.add(result);
+                }
+            }
+        }
+        return actualResult;
+    }
+
+    private List<TrainPurchaseTicketRespDTO> selectComplexSeats(SelectSeatDTO requestParam, List<String> trainCarriageList, List<Integer> trainStationCarriageRemainingTicket) {
+        String trainId = requestParam.getRequestParam().getTrainId();
+        String departure = requestParam.getRequestParam().getDeparture();
+        String arrival = requestParam.getRequestParam().getArrival();
+        List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails = requestParam.getPassengerSeatDetails();
+        List<TrainPurchaseTicketRespDTO> actualResult = new ArrayList<>();
+        Map<String, Integer> demotionStockNumMap = new LinkedHashMap<>();
+        Map<String, int[][]> actualSeatsMap = new HashMap<>();
+        Map<String, int[][]> carriagesNumberSeatsMap = new HashMap<>();
+        String carriagesNumber;
+        // 多人分配同一车厢邻座
+        for (int i = 0; i < trainStationCarriageRemainingTicket.size(); i++) {
+            carriagesNumber = trainCarriageList.get(i);
+            List<String> listAvailableSeat = listAvailableSeat(trainId, carriagesNumber, requestParam.getSeatType(), departure, arrival);
+            int[][] actualSeats = new int[2][3];
+            for (int j = 1; j < 3; j++) {
+                for (int k = 1; k < 4; k++) {
+                    // 当前默认按照复兴号商务座排序,后续这里需要按照简单工厂对车类型进行获取 y 轴
+                    actualSeats[j - 1][k - 1] = listAvailableSeat.contains("0" + j + SeatNumberUtil.convert(0, k)) ? 0 : 1;
+                }
+            }
+            int[][] actualSeatsTranscript = deepCopy(actualSeats);
+            List<int[][]> actualSelects = new ArrayList<>();
+            List<List<PurchaseTicketPassengerDetailDTO>> splitPassengerSeatDetails = ListUtil.split(passengerSeatDetails, 2);
+            for (List<PurchaseTicketPassengerDetailDTO> each : splitPassengerSeatDetails) {
+                int[][] select = SeatSelection.adjacent(each.size(), actualSeatsTranscript);
+                if (select != null) {
+                    for (int[] ints : select) {
+                        actualSeatsTranscript[ints[0] - 1][ints[1] - 1] = 1;
+                    }
+                    actualSelects.add(select);
+                }
+            }
+            if (actualSelects.size() == splitPassengerSeatDetails.size()) {
+                int[][] actualSelect = null;
+                for (int j = 0; j < actualSelects.size(); j++) {
+                    if (j == 0) {
+                        actualSelect = mergeArrays(actualSelects.get(j), actualSelects.get(j + 1));
+                    }
+                    if (j != 0 && actualSelects.size() > 2) {
+                        actualSelect = mergeArrays(actualSelect, actualSelects.get(j + 1));
+                    }
+                }
+                carriagesNumberSeatsMap.put(carriagesNumber, actualSelect);
+                break;
+            }
+            int demotionStockNum = 0;
+            for (int[] actualSeat : actualSeats) {
+                for (int i1 : actualSeat) {
+                    if (i1 == 0) {
+                        demotionStockNum++;
+                    }
+                }
+            }
+            demotionStockNumMap.putIfAbsent(carriagesNumber, demotionStockNum);
+            actualSeatsMap.putIfAbsent(carriagesNumber, actualSeats);
+        }
+        // 如果邻座算法无法匹配,尝试对用户进行降级分配:同车厢不邻座
+        if (CollUtil.isEmpty(carriagesNumberSeatsMap)) {
+            for (Map.Entry<String, Integer> entry : demotionStockNumMap.entrySet()) {
+                String carriagesNumberBack = entry.getKey();
+                int demotionStockNumBack = entry.getValue();
+                if (demotionStockNumBack > passengerSeatDetails.size()) {
+                    int[][] seats = actualSeatsMap.get(carriagesNumberBack);
+                    int[][] nonAdjacentSeats = SeatSelection.nonAdjacent(passengerSeatDetails.size(), seats);
+                    if (Objects.equals(nonAdjacentSeats.length, passengerSeatDetails.size())) {
+                        carriagesNumberSeatsMap.put(carriagesNumberBack, nonAdjacentSeats);
+                        break;
+                    }
+                }
+            }
+        }
+        // 如果同车厢也已无法匹配,则对用户座位再次降级:不同车厢不邻座
+        if (CollUtil.isEmpty(carriagesNumberSeatsMap)) {
+            int undistributedPassengerSize = passengerSeatDetails.size();
+            for (Map.Entry<String, Integer> entry : demotionStockNumMap.entrySet()) {
+                String carriagesNumberBack = entry.getKey();
+                int demotionStockNumBack = entry.getValue();
+                int[][] seats = actualSeatsMap.get(carriagesNumberBack);
+                int[][] nonAdjacentSeats = SeatSelection.nonAdjacent(Math.min(undistributedPassengerSize, demotionStockNumBack), seats);
+                undistributedPassengerSize = undistributedPassengerSize - demotionStockNumBack;
+                carriagesNumberSeatsMap.put(entry.getKey(), nonAdjacentSeats);
+            }
+        }
+        // 乘车人员在单一车厢座位不满足,触发乘车人元分布在不同车厢
+        int count = (int) carriagesNumberSeatsMap.values().stream()
+                .flatMap(Arrays::stream)
+                .count();
+        if (CollUtil.isNotEmpty(carriagesNumberSeatsMap) && passengerSeatDetails.size() == count) {
+            int countNum = 0;
+            for (Map.Entry<String, int[][]> entry : carriagesNumberSeatsMap.entrySet()) {
+                List<String> selectSeats = new ArrayList<>();
+                for (int[] ints : entry.getValue()) {
+                    selectSeats.add("0" + ints[0] + SeatNumberUtil.convert(0, ints[1]));
+                }
+                for (String selectSeat : selectSeats) {
+                    TrainPurchaseTicketRespDTO result = new TrainPurchaseTicketRespDTO();
+                    PurchaseTicketPassengerDetailDTO currentTicketPassenger = passengerSeatDetails.get(countNum++);
+                    result.setSeatNumber(selectSeat);
+                    result.setSeatType(currentTicketPassenger.getSeatType());
+                    result.setCarriageNumber(entry.getKey());
+                    result.setPassengerId(currentTicketPassenger.getPassengerId());
+                    actualResult.add(result);
+                }
+            }
+        }
+        return actualResult;
+    }
+
+
+    public List<String> listAvailableSeat(String trainId, String carriageNumber, Integer seatType, String departure, String arrival) {
+        LambdaQueryWrapper<SeatDO> queryWrapper = Wrappers.lambdaQuery(SeatDO.class)
+                .eq(SeatDO::getTrainId, trainId)
+                .eq(SeatDO::getCarriageNumber, carriageNumber)
+                .eq(SeatDO::getSeatType, seatType)
+                .eq(SeatDO::getStartStation, departure)
+                .eq(SeatDO::getEndStation, arrival)
+                .eq(SeatDO::getSeatStatus, 0)
+                .select(SeatDO::getSeatNumber);
+        List<SeatDO> seatDOList = seatMapper.selectList(queryWrapper);
+        return seatDOList.stream().map(SeatDO::getSeatNumber).collect(Collectors.toList());
+    }
+
+    public static int[][] mergeArrays(int[][] array1, int[][] array2) {
+        List<int[]> list = new ArrayList<>(Arrays.asList(array1));
+        list.addAll(Arrays.asList(array2));
+        return list.toArray(new int[0][]);
+    }
+
+    public static int[][] deepCopy(int[][] originalArray) {
+        int[][] copy = new int[originalArray.length][originalArray[0].length];
+        for (int i = 0; i < originalArray.length; i++) {
+            System.arraycopy(originalArray[i], 0, copy[i], 0, originalArray[i].length);
+        }
+        return copy;
+    }
+
+    protected TrainSeatBaseDTO buildTrainSeatBaseDTO(SelectSeatDTO requestParam) {
+        return TrainSeatBaseDTO.builder()
+                .trainId(requestParam.getRequestParam().getTrainId())
+                .departure(requestParam.getRequestParam().getDeparture())
+                .arrival(requestParam.getRequestParam().getArrival())
+                .chooseSeatList(requestParam.getRequestParam().getChooseSeats())
+                .passengerSeatDetails(requestParam.getPassengerSeatDetails())
+                .build();
+    }
+
+}

+ 180 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/service/handler/TrainSeatTypeSelector.java

@@ -0,0 +1,180 @@
+package com.sf.service.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.sf.dto.domain.SelectSeatDTO;
+import com.sf.dto.remote.PassengerRespDTO;
+import com.sf.dto.req.PurchaseTicketPassengerDetailDTO;
+import com.sf.dto.req.PurchaseTicketReqDTO;
+import com.sf.dto.resp.TrainPurchaseTicketRespDTO;
+import com.sf.entity.TrainStationPriceDO;
+import com.sf.mapper.TrainStationPriceMapper;
+import com.sf.remote.UserRemoteService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.opengoofy.index12306.framework.starter.convention.exception.RemoteException;
+import org.opengoofy.index12306.framework.starter.convention.exception.ServiceException;
+import org.opengoofy.index12306.framework.starter.convention.result.Result;
+import org.opengoofy.index12306.framework.starter.designpattern.strategy.AbstractStrategyChoose;
+import org.opengoofy.index12306.frameworks.starter.user.core.UserContext;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.stream.Collectors;
+
+/**
+ * 购票时列车座位选择器
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public final class TrainSeatTypeSelector {
+
+    private final UserRemoteService userRemoteService;
+    private final TrainStationPriceMapper trainStationPriceMapper;
+    private final AbstractStrategyChoose abstractStrategyChoose;
+    // 从spring容器中获取线程池
+    private final ThreadPoolExecutor selectSeatThreadPoolExecutor;
+
+    private final TrainBusinessClassPurchaseTicketHandler handler;
+
+    // 选座逻辑  传来的参数有 trainType requestParam
+    // 入参有  车次类型 车次id 乘车人列表(只有id和座位类型) 选择座位列表  出发和到达站点
+    // 出参有  乘车人ID、姓名、证件类型、证件号、手机号
+    //        用户类型、席别类型、车厢号、座位号、座位金额 (10个参数)
+    public List<TrainPurchaseTicketRespDTO> select(Integer trainType, PurchaseTicketReqDTO requestParam) {
+        // 获取乘车人列表
+        List<PurchaseTicketPassengerDetailDTO> passengerDetails = requestParam.getPassengers();
+        // 按照乘车人的座位类型分组  <0,List<>> <1,<List>>
+        // 获取的结果 是座位类型对应的数值作为key,这种类型的乘车人列表作为value
+        Map<Integer, List<PurchaseTicketPassengerDetailDTO>> seatTypeMap = passengerDetails.stream()
+                .collect(Collectors.groupingBy(PurchaseTicketPassengerDetailDTO::getSeatType));
+        // 使用并发安全的 ArrayList  接收最终结果
+        List<TrainPurchaseTicketRespDTO> actualResult = new CopyOnWriteArrayList<>();
+
+        // 当座位类型有多个 使用并行
+        if (seatTypeMap.size() > 1) {
+            // List<TrainPurchaseTicketRespDTO>是最终结果 也是一个线程的返回结果
+            // Future<List<TrainPurchaseTicketRespDTO>>  使用future接收
+            // List<Future<List<TrainPurchaseTicketRespDTO>>>  是多个线程的返回结果
+            List<Future<List<TrainPurchaseTicketRespDTO>>> futureResults = new ArrayList<>();
+            // 遍历map
+            seatTypeMap.forEach((seatType, passengerSeatDetails) -> {
+                // 线程池参数如何设置?详情查看:https://nageoffer.com/12306/question
+                Future<List<TrainPurchaseTicketRespDTO>> completableFuture =
+                        // 如果是 Runnable 使用execute进行提交
+                        // 如果是 Callable 使用submit进行提交
+                        selectSeatThreadPoolExecutor.submit(new Callable<List<TrainPurchaseTicketRespDTO>>() {
+                            @Override
+                            public List<TrainPurchaseTicketRespDTO> call() throws Exception {
+                                // 根据 火车类型 座位类型 请求参数 乘车人数据 进行分配座位
+                                return distributeSeats(trainType, seatType, requestParam, passengerSeatDetails);
+                            }
+                        });
+
+                futureResults.add(completableFuture);
+            });
+            // 并行流极端情况下有坑,详情参考:https://nageoffer.com/12306/question
+            // 获取并行流 parallelStream
+            futureResults.parallelStream().forEach(completableFuture -> {
+                try {
+                    // 如果有三个线程  就把三个线程的返回结果依次添加进来
+                    actualResult.addAll(completableFuture.get());
+                } catch (Exception e) {
+                    throw new ServiceException("站点余票不足,请尝试更换座位类型或选择其它站点");
+                }
+            });
+        } else {
+            // 只有一种座位类型 使用串行
+            // 遍历元素
+            seatTypeMap.forEach((seatType, passengerSeatDetails) -> {
+                // 根据 火车类型 座位类型 请求参数 乘车人数据 进行分配座位
+                List<TrainPurchaseTicketRespDTO> aggregationResult = distributeSeats(
+                        trainType, seatType, requestParam, passengerSeatDetails);
+                //  TrainPurchaseTicketRespDTO result = new TrainPurchaseTicketRespDTO();
+                //                    PurchaseTicketPassengerDetailDTO currentTicketPassenger = passengerSeatDetails.get(countNum++);
+                //                    result.setSeatNumber(selectSeat);
+                //                    result.setSeatType(currentTicketPassenger.getSeatType());
+                //                    result.setCarriageNumber(entry.getKey());
+                //                    result.setPassengerId(currentTicketPassenger.getPassengerId());
+                //                    actualResult.add(result);
+                // 此时得到的结果有  座位号、席别类型、车厢号、乘车人ID (4个参数)
+                actualResult.addAll(aggregationResult);
+            });
+        }
+        if (CollUtil.isEmpty(actualResult) || !Objects.equals(actualResult.size(), passengerDetails.size())) {
+            throw new ServiceException("站点余票不足,请尝试更换座位类型或选择其它站点");
+        }
+        // 拿出结果中的 乘车人id 组成list
+        // 提供给远程调用
+        List<String> passengerIds = actualResult.stream()
+                .map(TrainPurchaseTicketRespDTO::getPassengerId)
+                .collect(Collectors.toList());
+
+        Result<List<PassengerRespDTO>> passengerRemoteResult;
+        List<PassengerRespDTO> passengerRemoteResultList;
+        try {
+            // 远程调用 调用user微服务获取乘车人信息
+            passengerRemoteResult = userRemoteService.listPassengerQueryByIds(UserContext.getUsername(), passengerIds);
+            if (!passengerRemoteResult.isSuccess()
+                    || CollUtil.isEmpty(passengerRemoteResultList = passengerRemoteResult.getData())) {
+                throw new RemoteException("用户服务远程调用查询乘车人相信信息错误");
+            }
+        } catch (Throwable ex) {
+            if (ex instanceof RemoteException) {
+                log.error("用户服务远程调用查询乘车人相信信息错误,当前用户:{},请求参数:{}", UserContext.getUsername(), passengerIds);
+            } else {
+                log.error("用户服务远程调用查询乘车人相信信息错误,当前用户:{},请求参数:{}", UserContext.getUsername(), passengerIds, ex);
+            }
+            throw ex;
+        }
+        actualResult.forEach(each -> {
+            String passengerId = each.getPassengerId();
+            // 筛选和当前乘车人一致的 数据
+            passengerRemoteResultList.stream()
+                    .filter(item -> Objects.equals(item.getId(), passengerId))
+                    .findFirst()
+                    .ifPresent(passenger -> {
+                        // 将乘车人数据设置进去
+                        //   乘车人ID、姓名、证件类型、证件号、手机号 (5个参数)
+                        each.setIdCard(passenger.getIdCard());
+                        each.setPhone(passenger.getPhone());
+                        each.setUserType(passenger.getDiscountType());
+                        each.setIdType(passenger.getIdType());
+                        each.setRealName(passenger.getRealName());
+                    });
+            // 查询t_train_station_price表中符合条件的数据
+            LambdaQueryWrapper<TrainStationPriceDO> lambdaQueryWrapper = Wrappers.lambdaQuery(TrainStationPriceDO.class)
+                    .eq(TrainStationPriceDO::getTrainId, requestParam.getTrainId())
+                    .eq(TrainStationPriceDO::getDeparture, requestParam.getDeparture())
+                    .eq(TrainStationPriceDO::getArrival, requestParam.getArrival())
+                    .eq(TrainStationPriceDO::getSeatType, each.getSeatType())
+                    .select(TrainStationPriceDO::getPrice);
+            TrainStationPriceDO trainStationPriceDO = trainStationPriceMapper.selectOne(lambdaQueryWrapper);
+            // 通过mybatis plus将价格参数设置进来  (1个参数)
+            each.setAmount(trainStationPriceDO.getPrice());
+        });
+        // 购买列车中间站点余票如何更新?详细查看:https://nageoffer.com/12306/question
+        // 锁定座位 避免卖重
+        return actualResult;
+    }
+
+    private List<TrainPurchaseTicketRespDTO> distributeSeats(Integer trainType, Integer seatType, PurchaseTicketReqDTO requestParam, List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails) {
+        // 构造策略模式的key值
+        SelectSeatDTO selectSeatDTO = SelectSeatDTO.builder()
+                .seatType(seatType)
+                .passengerSeatDetails(passengerSeatDetails)
+                .requestParam(requestParam)
+                .build();
+        return handler.selectSeats(selectSeatDTO);
+    }
+}

+ 98 - 2
12306-demo/my-ticket-service/src/main/java/com/sf/service/impl/TicketServiceImpl.java

@@ -9,17 +9,29 @@ import com.google.common.collect.Lists;
 import com.sf.dto.SeatClassDTO;
 import com.sf.dto.TicketListDTO;
 import com.sf.dto.domain.RouteDTO;
+import com.sf.dto.remote.TicketOrderCreateRemoteReqDTO;
+import com.sf.dto.remote.TicketOrderItemCreateRemoteReqDTO;
+import com.sf.dto.req.PurchaseTicketReqDTO;
 import com.sf.dto.req.TicketPageQueryReqDTO;
+import com.sf.dto.resp.TicketOrderDetailRespDTO;
 import com.sf.dto.resp.TicketPageQueryRespDTO;
+import com.sf.dto.resp.TicketPurchaseRespDTO;
+import com.sf.dto.resp.TrainPurchaseTicketRespDTO;
 import com.sf.entity.*;
 import com.sf.mapper.*;
+import com.sf.remote.TicketOrderRemoteService;
 import com.sf.service.TicketService;
+import com.sf.service.handler.TrainSeatTypeSelector;
 import com.sf.util.DateUtil;
 import com.sf.util.StationCalculateUtil;
 import com.sf.util.TimeStringComparator;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.opengoofy.index12306.framework.starter.cache.DistributedCache;
 import org.opengoofy.index12306.framework.starter.cache.toolkit.CacheUtil;
+import org.opengoofy.index12306.framework.starter.convention.exception.ServiceException;
+import org.opengoofy.index12306.framework.starter.convention.result.Result;
+import org.opengoofy.index12306.frameworks.starter.user.core.UserContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;
@@ -34,6 +46,7 @@ import static com.sf.constant.Index12306Constant.ADVANCE_TICKET_DAY;
 import static com.sf.constant.RedisKeyConstant.*;
 import static com.sf.util.DateUtil.convertDateToLocalTime;
 
+@Slf4j
 @RequiredArgsConstructor
 @Service
 public class TicketServiceImpl extends ServiceImpl<TicketMapper, TicketDO> implements TicketService {
@@ -46,6 +59,9 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, TicketDO> imple
     private final TrainStationMapper trainStationMapper;
     private final SeatMapper seatMapper;
 
+    private final TrainSeatTypeSelector trainSeatTypeSelector;
+    private final TicketOrderRemoteService ticketOrderRemoteService;
+
     @Override
     public TicketPageQueryRespDTO pageListTicketQueryByMysql(TicketPageQueryReqDTO requestParam) {
         System.out.println("TicketPageQueryRespDTO pageListTicketQueryByMysql");
@@ -141,7 +157,6 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, TicketDO> imple
                 //  Map<String, String> seatMarginMap = seatMarginCacheLoader.load(
                 //       String.valueOf(each.getTrainId()), seatType, item.getDeparture(), item.getArrival());
                 Map<String, Map<String, String>> trainStationRemainingTicketMaps = new LinkedHashMap<>();
-                String keySuffix = CacheUtil.buildKey(trainId, departure, arrival);
                 // INSERT INTO `t_train` (`id`, `train_number`, `train_type`, `train_tag`, `train_brand`, `start_station`, `end_station`, `start_region`, `end_region`, `sale_time`, `sale_status`, `departure_time`, `arrival_time`, `create_time`, `update_time`, `del_flag`)
                 // VALUES
                 //	(1, 'G35', 0, '0,1,2', '0,6', '北京南', '宁波', '北京', '宁波', '2023-05-15 14:30:00', 0, '2023-06-01 09:56:00', '2023-06-01 15:14:00', '2023-06-01 20:45:00', '2023-06-01 20:45:00', 0);
@@ -159,7 +174,6 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, TicketDO> imple
                 // 假设不为空
                 // G高铁是0 D动车1 Z直达是2
                 switch (trainDO.getTrainType()) {
-                    // TODO 通过已有列车类型座位枚举重构
                     case 0 -> {
                         for (RouteDTO routeDTO : routeDTOList) {
                             Map<String, String> trainStationRemainingTicket = new LinkedHashMap<>();
@@ -194,6 +208,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, TicketDO> imple
                     }
                 }
 
+                String keySuffix = CacheUtil.buildKey(trainId, departure, arrival);
                 Map<String, String> seatMarginMap = trainStationRemainingTicketMaps.get(TRAIN_STATION_REMAINING_TICKET + keySuffix);
                 int quantity = Integer.parseInt(seatMarginMap.get(String.valueOf(item.getSeatType())));
 
@@ -291,6 +306,87 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, TicketDO> imple
                 .build();
     }
 
+    @Override
+    public TicketPurchaseRespDTO purchaseTicketsV2(PurchaseTicketReqDTO requestParam) {
+        return executePurchaseTickets(requestParam);
+    }
+
+    public TicketPurchaseRespDTO executePurchaseTickets(PurchaseTicketReqDTO requestParam) {
+        List<TicketOrderDetailRespDTO> ticketOrderDetailResults = new ArrayList<>();
+        String trainId = requestParam.getTrainId();
+        // 节假日高并发购票Redis能扛得住么?详情查看:https://nageoffer.com/12306/question
+        TrainDO trainDO = trainMapper.selectById(trainId);
+        List<TrainPurchaseTicketRespDTO> trainPurchaseTicketResults = trainSeatTypeSelector.select(trainDO.getTrainType(), requestParam);
+        List<TicketDO> ticketDOList = trainPurchaseTicketResults.stream()
+                .map(each -> TicketDO.builder()
+                        .username(UserContext.getUsername())
+                        .trainId(Long.parseLong(requestParam.getTrainId()))
+                        .carriageNumber(each.getCarriageNumber())
+                        .seatNumber(each.getSeatNumber())
+                        .passengerId(each.getPassengerId())
+                        .ticketStatus(0) //未支付状态
+                        .build())
+                .toList();
+        saveBatch(ticketDOList);
+        Result<String> ticketOrderResult;
+        try {
+            List<TicketOrderItemCreateRemoteReqDTO> orderItemCreateRemoteReqDTOList = new ArrayList<>();
+            trainPurchaseTicketResults.forEach(each -> {
+                TicketOrderItemCreateRemoteReqDTO orderItemCreateRemoteReqDTO = TicketOrderItemCreateRemoteReqDTO.builder()
+                        .amount(each.getAmount())
+                        .carriageNumber(each.getCarriageNumber())
+                        .seatNumber(each.getSeatNumber())
+                        .idCard(each.getIdCard())
+                        .idType(each.getIdType())
+                        .phone(each.getPhone())
+                        .seatType(each.getSeatType())
+                        .ticketType(each.getUserType())
+                        .realName(each.getRealName())
+                        .build();
+                TicketOrderDetailRespDTO ticketOrderDetailRespDTO = TicketOrderDetailRespDTO.builder()
+                        .amount(each.getAmount())
+                        .carriageNumber(each.getCarriageNumber())
+                        .seatNumber(each.getSeatNumber())
+                        .idCard(each.getIdCard())
+                        .idType(each.getIdType())
+                        .seatType(each.getSeatType())
+                        .ticketType(each.getUserType())
+                        .realName(each.getRealName())
+                        .build();
+                orderItemCreateRemoteReqDTOList.add(orderItemCreateRemoteReqDTO);
+                ticketOrderDetailResults.add(ticketOrderDetailRespDTO);
+            });
+            LambdaQueryWrapper<TrainStationRelationDO> queryWrapper = Wrappers.lambdaQuery(TrainStationRelationDO.class)
+                    .eq(TrainStationRelationDO::getTrainId, trainId)
+                    .eq(TrainStationRelationDO::getDeparture, requestParam.getDeparture())
+                    .eq(TrainStationRelationDO::getArrival, requestParam.getArrival());
+            TrainStationRelationDO trainStationRelationDO = trainStationRelationMapper.selectOne(queryWrapper);
+            TicketOrderCreateRemoteReqDTO orderCreateRemoteReqDTO = TicketOrderCreateRemoteReqDTO.builder()
+                    .departure(requestParam.getDeparture())
+                    .arrival(requestParam.getArrival())
+                    .orderTime(new Date())
+                    .source(0) //购票方式  互联网购票是0
+                    .trainNumber(trainDO.getTrainNumber())
+                    .departureTime(trainStationRelationDO.getDepartureTime())
+                    .arrivalTime(trainStationRelationDO.getArrivalTime())
+                    .ridingDate(trainStationRelationDO.getDepartureTime())
+                    .userId(UserContext.getUserId())
+                    .username(UserContext.getUsername())
+                    .trainId(Long.parseLong(requestParam.getTrainId()))
+                    .ticketOrderItems(orderItemCreateRemoteReqDTOList)
+                    .build();
+            ticketOrderResult = ticketOrderRemoteService.createTicketOrder(orderCreateRemoteReqDTO);
+            if (!ticketOrderResult.isSuccess() || StrUtil.isBlank(ticketOrderResult.getData())) {
+                log.error("订单服务调用失败,返回结果:{}", ticketOrderResult.getMessage());
+                throw new ServiceException("订单服务调用失败");
+            }
+        } catch (Throwable ex) {
+            log.error("远程调用订单服务创建错误,请求参数:{}", JSON.toJSONString(requestParam), ex);
+            throw ex;
+        }
+        return new TicketPurchaseRespDTO(ticketOrderResult.getData(), ticketOrderDetailResults);
+    }
+
 
     private List<String> buildDepartureStationList(List<TicketListDTO> seatResults) {
         return seatResults.stream().map(TicketListDTO::getDeparture).distinct().collect(Collectors.toList());

+ 79 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/util/CarriageVacantSeatCalculateUtil.java

@@ -0,0 +1,79 @@
+/*
+ * 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.util;
+
+import cn.hutool.core.lang.Pair;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.PriorityQueue;
+
+/**
+ * 座位统计工具类
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public final class CarriageVacantSeatCalculateUtil {
+
+    /**
+     * 座位统计方法
+     *
+     * @param actualSeats 座位选座情况二位数组
+     * @param n           列数
+     * @param m           行数
+     * @return 空余座位集合小根堆
+     */
+    public static PriorityQueue<List<Pair<Integer, Integer>>> buildCarriageVacantSeatList(int[][] actualSeats, int n, int m) {
+        PriorityQueue<List<Pair<Integer, Integer>>> vacantSeatQueue = new PriorityQueue<>(Comparator.comparingInt(List::size));
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < m; j++) {
+                if (actualSeats[i][j] == 0) {
+                    List<Pair<Integer, Integer>> res = new ArrayList<>();
+                    int k = j;
+                    for (; k < m; k++) {
+                        if (actualSeats[i][k] == 1) break;
+                        res.add(new Pair<>(i, k));
+                    }
+                    j = k;
+                    vacantSeatQueue.add(res);
+                }
+            }
+        }
+        return vacantSeatQueue;
+    }
+
+    /**
+     * 空余座位统计方法
+     *
+     * @param actualSeats 座位状态数组
+     * @param n
+     * @param m
+     * @return 空余座位集合
+     */
+    public static List<Pair<Integer, Integer>> buildCarriageVacantSeatList2(int[][] actualSeats, int n, int m) {
+        List<Pair<Integer, Integer>> vacantSeatList = new ArrayList<>(16);
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < m; j++) {
+                if (actualSeats[i][j] == 0) {
+                    vacantSeatList.add(new Pair<>(i, j));
+                }
+            }
+        }
+        return vacantSeatList;
+    }
+}

+ 75 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/util/SeatNumberUtil.java

@@ -0,0 +1,75 @@
+/*
+ * 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.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 座位号转换工具
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public final class SeatNumberUtil {
+
+    /**
+     * 复兴号-商务座
+     */
+    private static final Map<Integer, String> TRAIN_BUSINESS_CLASS_SEAT_NUMBER_MAP = new HashMap<>();
+
+    /**
+     * 复兴号-一等座
+     */
+    private static final Map<Integer, String> TRAIN_FIRST_CLASS_SEAT_NUMBER_MAP = new HashMap<>();
+
+    /**
+     * 复兴号-二等座
+     */
+    private static final Map<Integer, String> TRAIN_SECOND_CLASS_SEAT_NUMBER_MAP = new HashMap<>();
+
+    static {
+        TRAIN_BUSINESS_CLASS_SEAT_NUMBER_MAP.put(1, "A");
+        TRAIN_BUSINESS_CLASS_SEAT_NUMBER_MAP.put(2, "C");
+        TRAIN_BUSINESS_CLASS_SEAT_NUMBER_MAP.put(3, "F");
+        TRAIN_FIRST_CLASS_SEAT_NUMBER_MAP.put(1, "A");
+        TRAIN_FIRST_CLASS_SEAT_NUMBER_MAP.put(2, "C");
+        TRAIN_FIRST_CLASS_SEAT_NUMBER_MAP.put(3, "D");
+        TRAIN_FIRST_CLASS_SEAT_NUMBER_MAP.put(4, "F");
+        TRAIN_SECOND_CLASS_SEAT_NUMBER_MAP.put(1, "A");
+        TRAIN_SECOND_CLASS_SEAT_NUMBER_MAP.put(2, "B");
+        TRAIN_SECOND_CLASS_SEAT_NUMBER_MAP.put(3, "C");
+        TRAIN_SECOND_CLASS_SEAT_NUMBER_MAP.put(4, "D");
+        TRAIN_SECOND_CLASS_SEAT_NUMBER_MAP.put(5, "F");
+    }
+
+    /**
+     * 根据类型转换座位号
+     *
+     * @param type 列车座位类型
+     * @param num  座位号
+     * @return 座位编号
+     */
+    public static String convert(int type, int num) {
+        String serialNumber = null;
+        switch (type) {
+            case 0 -> serialNumber = TRAIN_BUSINESS_CLASS_SEAT_NUMBER_MAP.get(num);
+            case 1 -> serialNumber = TRAIN_FIRST_CLASS_SEAT_NUMBER_MAP.get(num);
+            case 2 -> serialNumber = TRAIN_SECOND_CLASS_SEAT_NUMBER_MAP.get(num);
+        }
+        return serialNumber;
+    }
+}

+ 51 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/util/checkseat/BitMapCheckSeat.java

@@ -0,0 +1,51 @@
+/*
+ * 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.util.checkseat;
+
+import org.opengoofy.index12306.framework.starter.cache.DistributedCache;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 抽象的验证座位实体类
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public interface BitMapCheckSeat {
+
+    /**
+     * 座位是否存在检查方法
+     *
+     * @param key              缓存Key
+     * @param convert          座位统计Map
+     * @param distributedCache 分布式缓存接口
+     * @return 判断座位是否存在 true or false
+     */
+    boolean checkSeat(String key, HashMap<Integer, Integer> convert, DistributedCache distributedCache);
+
+    /**
+     * 检查座位是否存在 v2 版本
+     *
+     * @param chooseSeatList 选择座位
+     * @param actualSeats    座位状态数组
+     * @param SEAT_Y_INT     坐标转换 Map
+     * @return
+     */
+    boolean checkChooseSeat(List<String> chooseSeatList, int[][] actualSeats, Map<Character, Integer> SEAT_Y_INT);
+}

+ 134 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/util/checkseat/SeatSelection.java

@@ -0,0 +1,134 @@
+/*
+ * 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.util.checkseat;
+
+import cn.hutool.core.collection.CollUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 座位选择器
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public class SeatSelection {
+
+    public static int[][] adjacent(int numSeats, int[][] seatLayout) {
+        int numRows = seatLayout.length;
+        int numCols = seatLayout[0].length;
+        List<int[]> selectedSeats = new ArrayList<>();
+        for (int i = 0; i < numRows; i++) {
+            for (int j = 0; j < numCols; j++) {
+                if (seatLayout[i][j] == 0) {
+                    int consecutiveSeats = 0;
+                    for (int k = j; k < numCols; k++) {
+                        if (seatLayout[i][k] == 0) {
+                            consecutiveSeats++;
+                            if (consecutiveSeats == numSeats) {
+                                for (int l = k - numSeats + 1; l <= k; l++) {
+                                    selectedSeats.add(new int[]{i, l});
+                                }
+                                break;
+                            }
+                        } else {
+                            consecutiveSeats = 0;
+                        }
+                    }
+                    if (!selectedSeats.isEmpty()) {
+                        break;
+                    }
+                }
+            }
+            if (!selectedSeats.isEmpty()) {
+                break;
+            }
+        }
+        if (CollUtil.isEmpty(selectedSeats)) {
+            return null;
+        }
+        int[][] actualSeat = new int[numSeats][2];
+        int i = 0;
+        for (int[] seat : selectedSeats) {
+            int row = seat[0] + 1;
+            int col = seat[1] + 1;
+            actualSeat[i][0] = row;
+            actualSeat[i][1] = col;
+            i++;
+        }
+        return actualSeat;
+    }
+
+    public static int[][] nonAdjacent(int numSeats, int[][] seatLayout) {
+        int numRows = seatLayout.length;
+        int numCols = seatLayout[0].length;
+        List<int[]> selectedSeats = new ArrayList<>();
+        for (int i = 0; i < numRows; i++) {
+            for (int j = 0; j < numCols; j++) {
+                if (seatLayout[i][j] == 0) {
+                    selectedSeats.add(new int[]{i, j});
+                    if (selectedSeats.size() == numSeats) {
+                        break;
+                    }
+                }
+            }
+            if (selectedSeats.size() == numSeats) {
+                break;
+            }
+        }
+        return convertToActualSeat(selectedSeats);
+    }
+
+    private static int[][] convertToActualSeat(List<int[]> selectedSeats) {
+        int[][] actualSeat = new int[selectedSeats.size()][2];
+        for (int i = 0; i < selectedSeats.size(); i++) {
+            int[] seat = selectedSeats.get(i);
+            int row = seat[0] + 1;
+            int col = seat[1] + 1;
+            actualSeat[i][0] = row;
+            actualSeat[i][1] = col;
+        }
+        return actualSeat;
+    }
+
+    public static void main(String[] args) {
+        int[][] seatLayout = {
+                {1, 1, 1, 1},
+                {1, 1, 1, 0},
+                {1, 1, 1, 0},
+                {0, 0, 0, 0}
+        };
+        int[][] select = adjacent(2, seatLayout);
+        System.out.println("成功预订相邻座位,座位位置为:");
+        assert select != null;
+        for (int[] ints : select) {
+            System.out.printf("第 %d 排,第 %d 列%n", ints[0], ints[1]);
+        }
+
+        int[][] seatLayoutTwo = {
+                {1, 0, 1, 1},
+                {1, 1, 0, 0},
+                {1, 1, 1, 0},
+                {0, 0, 0, 0}
+        };
+        int[][] selectTwo = nonAdjacent(3, seatLayoutTwo);
+        System.out.println("成功预订不相邻座位,座位位置为:");
+        for (int[] ints : selectTwo) {
+            System.out.printf("第 %d 排,第 %d 列%n", ints[0], ints[1]);
+        }
+    }
+}

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

@@ -0,0 +1,25 @@
+/*
+ * 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.util.checkseat;
+
+/**
+ * 高铁验证座位接口
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public interface TrainBitMapCheckSeat extends BitMapCheckSeat {
+}

+ 104 - 0
12306-demo/my-ticket-service/src/main/java/com/sf/util/checkseat/TrainBusinessCheckSeat.java

@@ -0,0 +1,104 @@
+/*
+ * 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.util.checkseat;
+
+import org.opengoofy.index12306.framework.starter.cache.DistributedCache;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 高铁商务座验证座位
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+public class TrainBusinessCheckSeat implements TrainBitMapCheckSeat {
+
+    /**
+     * 高铁商务座是否存在检查方法
+     *
+     * @param key              缓存Key
+     * @param convert          座位统计Map
+     * @param distributedCache 分布式缓存接口
+     * @return 判断座位是否存在 true or false
+     */
+    @Override
+    public boolean checkSeat(final String key, HashMap<Integer, Integer> convert, DistributedCache distributedCache) {
+        boolean flag = false;
+        ValueOperations<String, String> opsForValue = ((StringRedisTemplate) distributedCache.getInstance()).opsForValue();
+        AtomicInteger matchCount = new AtomicInteger(0);
+        for (int i = 0; i < 3; i++) {
+            int cnt = 0;
+            if (convert.containsKey(i)) {
+                for (int j = 0; j < 2; j++) {
+                    Boolean bit = opsForValue.getBit(key, i + j * 3);
+                    if (null != bit && bit) {
+                        cnt = cnt + 1;
+                    }
+                    if (cnt == convert.get(i)) {
+                        matchCount.getAndIncrement();
+                        break;
+                    }
+                }
+                if (cnt != convert.get(i)) {
+                    break;
+                }
+            }
+            if (matchCount.get() == convert.size()) {
+                flag = true;
+                break;
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 高铁商务座选择座位是否被占用
+     *
+     * @param chooseSeatList 选择座位
+     * @param actualSeats    座位状态数组
+     * @param SEAT_Y_INT     坐标转换 Map
+     * @return
+     */
+    @Override
+    public boolean checkChooseSeat(List<String> chooseSeatList, int[][] actualSeats, Map<Character, Integer> SEAT_Y_INT) {
+        boolean isExists = true;
+        for (int i = 0; i < chooseSeatList.size(); i++) {
+            if (chooseSeatList.size() == 1) {
+                String chooseSeat = chooseSeatList.get(i);
+                int seatX = Integer.parseInt(chooseSeat.substring(1));
+                int seatY = SEAT_Y_INT.get(chooseSeat.charAt(0));
+                if (actualSeats[seatX][seatY] != 0 && actualSeats[1][seatY] != 0) {
+                    break;
+                }
+            } else {
+                String chooseSeat = chooseSeatList.get(i);
+                int seatX = Integer.parseInt(chooseSeat.substring(1));
+                int seatY = SEAT_Y_INT.get(chooseSeat.charAt(0));
+                if (actualSeats[seatX][seatY] != 0) {
+                    isExists = false;
+                    break;
+                }
+            }
+        }
+        return isExists;
+    }
+}

+ 11 - 0
12306-demo/my-user-service/src/main/java/com/sf/controller/PassengerController.java

@@ -1,5 +1,6 @@
 package com.sf.controller;
 
+import com.sf.dto.resp.PassengerActualRespDTO;
 import com.sf.dto.resp.PassengerRespDTO;
 import com.sf.service.IPassengerService;
 import lombok.RequiredArgsConstructor;
@@ -7,6 +8,7 @@ import org.opengoofy.index12306.framework.starter.convention.result.Result;
 import org.opengoofy.index12306.framework.starter.web.Results;
 import org.opengoofy.index12306.frameworks.starter.user.core.UserContext;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
@@ -24,4 +26,13 @@ public class PassengerController {
         return Results.success(passengerService.listPassengerQueryByUsername(username));
     }
 
+
+    /**
+     * 根据乘车人 ID 集合查询乘车人列表
+     */
+    @GetMapping("/api/user-service/inner/passenger/actual/query/ids")
+    public Result<List<PassengerActualRespDTO>> listPassengerQueryByIds(@RequestParam("username") String username, @RequestParam("ids") List<Long> ids) {
+        return Results.success(passengerService.listPassengerQueryByIds(username, ids));
+    }
+
 }

+ 79 - 0
12306-demo/my-user-service/src/main/java/com/sf/dto/resp/PassengerActualRespDTO.java

@@ -0,0 +1,79 @@
+/*
+ * 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.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * 乘车人真实返回参数,不包含脱敏信息
+ * 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
+ */
+@Data
+@Accessors(chain = true)
+public class PassengerActualRespDTO {
+
+    /**
+     * 乘车人id
+     */
+    private String id;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    /**
+     * 证件类型
+     */
+    private Integer idType;
+
+    /**
+     * 证件号码
+     */
+    private String idCard;
+
+    /**
+     * 优惠类型
+     */
+    private Integer discountType;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 添加日期
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date createDate;
+
+    /**
+     * 审核状态
+     */
+    private Integer verifyStatus;
+}

+ 11 - 0
12306-demo/my-user-service/src/main/java/com/sf/service/IPassengerService.java

@@ -18,6 +18,7 @@
 package com.sf.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.sf.dto.resp.PassengerActualRespDTO;
 import com.sf.dto.resp.PassengerRespDTO;
 import com.sf.entity.PassengerDO;
 
@@ -36,4 +37,14 @@ public interface IPassengerService extends IService<PassengerDO> {
      * @return 乘车人返回列表
      */
     List<PassengerRespDTO> listPassengerQueryByUsername(String username);
+
+    /**
+     * 根据乘车人 ID 集合查询乘车人列表
+     *
+     * @param username 用户名
+     * @param ids      乘车人 ID 集合
+     * @return 乘车人返回列表
+     */
+    List<PassengerActualRespDTO> listPassengerQueryByIds(String username, List<Long> ids);
+
 }

+ 17 - 0
12306-demo/my-user-service/src/main/java/com/sf/service/impl/PassengerServiceImpl.java

@@ -1,7 +1,11 @@
 package com.sf.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
+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.sf.dto.resp.PassengerActualRespDTO;
 import com.sf.dto.resp.PassengerRespDTO;
 import com.sf.entity.PassengerDO;
 import com.sf.mapper.PassengerMapper;
@@ -11,6 +15,7 @@ import org.opengoofy.index12306.framework.starter.common.toolkit.BeanUtil;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 @Service
 @RequiredArgsConstructor
@@ -31,4 +36,16 @@ public class PassengerServiceImpl extends ServiceImpl<PassengerMapper, Passenger
                 .toList();
         return passengerRespDTOS;
     }
+
+    @Override
+    public List<PassengerActualRespDTO> listPassengerQueryByIds(String username, List<Long> ids) {
+        LambdaQueryWrapper<PassengerDO> queryWrapper = Wrappers.lambdaQuery(PassengerDO.class)
+                .eq(PassengerDO::getUsername, username);
+        List<PassengerDO> passengerDOList = passengerMapper.selectList(queryWrapper);
+        List<PassengerActualRespDTO> respDTOS =
+                passengerDOList.stream().filter(passengerDO -> ids.contains(passengerDO.getId()))
+                        .map(each -> BeanUtil.convert(each, PassengerActualRespDTO.class))
+                        .collect(Collectors.toList());
+        return respDTOS;
+    }
 }