From bc7389af9b2841ab2e5ab44c595b4a3bde195abf Mon Sep 17 00:00:00 2001
From: zhouwei <zhouwei@ssish.com>
Date: Mon, 14 Apr 2025 13:26:56 +0800
Subject: [PATCH] init

---
 .gitignore                                    |  33 ++
 .mvn/wrapper/maven-wrapper.properties         |  18 ++
 Dockerfile-prd                                |   5 +
 Dockerfile-qa                                 |   5 +
 README.md                                     |   3 +-
 deployment.yaml                               |  42 +++
 mvnw                                          | 250 ++++++++++++++++
 mvnw.cmd                                      | 146 +++++++++
 pom.xml                                       | 131 ++++++++
 service.yaml                                  |  35 +++
 .../securitylink/GatewayApplication.java      |  11 +
 .../nanyan/securitylink/common/Constant.java  |  39 +++
 .../nanyan/securitylink/common/MsgCode.java   |  38 +++
 .../config/FailoverProperties.java            |  29 ++
 .../securitylink/config/GatewayConfig.java    |  26 ++
 .../config/RestTemplateConfig.java            |  13 +
 .../securitylink/config/SchedulerConfig.java  |  17 ++
 .../controller/HealthController.java          |  13 +
 .../nanyan/securitylink/dto/TranslateDTO.java |  10 +
 .../securitylink/dto/TranslateInputDTO.java   |   9 +
 .../entity/ChatCompletionResponse.java        |  16 +
 .../securitylink/entity/ChatInputData.java    |  12 +
 .../nanyan/securitylink/entity/Choice.java    |  11 +
 .../nanyan/securitylink/entity/Message.java   |  10 +
 .../entity/PromptTokensDetails.java           |   8 +
 .../com/nanyan/securitylink/entity/Usage.java |  13 +
 .../securitylink/entity/UserHeader.java       |  40 +++
 .../securitylink/entity/UserThreadLocal.java  |  14 +
 .../securitylink/execption/BaseException.java |  20 ++
 .../execption/HeaderException.java            |   9 +
 .../filter/FailoverGatewayFilterFactory.java  | 208 +++++++++++++
 .../schedule/RefreshConfigSync.java           |  39 +++
 .../schedule/ScheduledTasksService.java       |  30 ++
 .../service/TokenRouteMappingService.java     |  34 +++
 .../nanyan/securitylink/utils/DateUtils.java  | 283 ++++++++++++++++++
 .../nanyan/securitylink/vo/AIResponse.java    |  15 +
 .../com/nanyan/securitylink/vo/CodeVO.java    |   9 +
 .../com/nanyan/securitylink/vo/Response.java  |  61 ++++
 .../com/nanyan/securitylink/vo/ResultVO.java  |  10 +
 src/main/resources/application-prd.properties |   6 +
 src/main/resources/application-qa.properties  |  26 ++
 src/main/resources/application.properties     |   7 +
 src/main/resources/log4j2.xml                 |  87 ++++++
 43 files changed, 1840 insertions(+), 1 deletion(-)
 create mode 100644 .gitignore
 create mode 100644 .mvn/wrapper/maven-wrapper.properties
 create mode 100644 Dockerfile-prd
 create mode 100644 Dockerfile-qa
 create mode 100644 deployment.yaml
 create mode 100644 mvnw
 create mode 100644 mvnw.cmd
 create mode 100644 pom.xml
 create mode 100644 service.yaml
 create mode 100644 src/main/java/com/nanyan/securitylink/GatewayApplication.java
 create mode 100644 src/main/java/com/nanyan/securitylink/common/Constant.java
 create mode 100644 src/main/java/com/nanyan/securitylink/common/MsgCode.java
 create mode 100644 src/main/java/com/nanyan/securitylink/config/FailoverProperties.java
 create mode 100644 src/main/java/com/nanyan/securitylink/config/GatewayConfig.java
 create mode 100644 src/main/java/com/nanyan/securitylink/config/RestTemplateConfig.java
 create mode 100644 src/main/java/com/nanyan/securitylink/config/SchedulerConfig.java
 create mode 100644 src/main/java/com/nanyan/securitylink/controller/HealthController.java
 create mode 100644 src/main/java/com/nanyan/securitylink/dto/TranslateDTO.java
 create mode 100644 src/main/java/com/nanyan/securitylink/dto/TranslateInputDTO.java
 create mode 100644 src/main/java/com/nanyan/securitylink/entity/ChatCompletionResponse.java
 create mode 100644 src/main/java/com/nanyan/securitylink/entity/ChatInputData.java
 create mode 100644 src/main/java/com/nanyan/securitylink/entity/Choice.java
 create mode 100644 src/main/java/com/nanyan/securitylink/entity/Message.java
 create mode 100644 src/main/java/com/nanyan/securitylink/entity/PromptTokensDetails.java
 create mode 100644 src/main/java/com/nanyan/securitylink/entity/Usage.java
 create mode 100644 src/main/java/com/nanyan/securitylink/entity/UserHeader.java
 create mode 100644 src/main/java/com/nanyan/securitylink/entity/UserThreadLocal.java
 create mode 100644 src/main/java/com/nanyan/securitylink/execption/BaseException.java
 create mode 100644 src/main/java/com/nanyan/securitylink/execption/HeaderException.java
 create mode 100644 src/main/java/com/nanyan/securitylink/filter/FailoverGatewayFilterFactory.java
 create mode 100644 src/main/java/com/nanyan/securitylink/schedule/RefreshConfigSync.java
 create mode 100644 src/main/java/com/nanyan/securitylink/schedule/ScheduledTasksService.java
 create mode 100644 src/main/java/com/nanyan/securitylink/service/TokenRouteMappingService.java
 create mode 100644 src/main/java/com/nanyan/securitylink/utils/DateUtils.java
 create mode 100644 src/main/java/com/nanyan/securitylink/vo/AIResponse.java
 create mode 100644 src/main/java/com/nanyan/securitylink/vo/CodeVO.java
 create mode 100644 src/main/java/com/nanyan/securitylink/vo/Response.java
 create mode 100644 src/main/java/com/nanyan/securitylink/vo/ResultVO.java
 create mode 100644 src/main/resources/application-prd.properties
 create mode 100644 src/main/resources/application-qa.properties
 create mode 100644 src/main/resources/application.properties
 create mode 100644 src/main/resources/log4j2.xml

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/.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/
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..aeccdfd
--- /dev/null
+++ b/.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
diff --git a/Dockerfile-prd b/Dockerfile-prd
new file mode 100644
index 0000000..4db625e
--- /dev/null
+++ b/Dockerfile-prd
@@ -0,0 +1,5 @@
+FROM openjdk:8
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENV SPRING_PROFILES_ACTIVE=prd
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/Dockerfile-qa b/Dockerfile-qa
new file mode 100644
index 0000000..ac81655
--- /dev/null
+++ b/Dockerfile-qa
@@ -0,0 +1,5 @@
+FROM openjdk:8
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENV SPRING_PROFILES_ACTIVE=qa
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/README.md b/README.md
index f930bb1..0f55acc 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
-# sl-gateway
+# sl-ai
 
+调用大模型的API服务
\ No newline at end of file
diff --git a/deployment.yaml b/deployment.yaml
new file mode 100644
index 0000000..b24fea2
--- /dev/null
+++ b/deployment.yaml
@@ -0,0 +1,42 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: sl-gateway
+  namespace: securitylink
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: sl-gateway
+  template:
+    metadata:
+      labels:
+        app: sl-gateway
+    spec:
+      containers:
+        - name: sl-gateway
+          image: 381492067916.dkr.ecr.ap-east-1.amazonaws.com/sl-gateway:latest
+          resources:
+            limits:
+              memory: "2Gi"
+              cpu: "1000m"
+            requests:
+              memory: "500Mi"
+              cpu: "500m"
+          ports:
+            - name: httpport
+              containerPort: 8080
+          livenessProbe:
+            tcpSocket:
+              port: 8080
+            initialDelaySeconds: 90
+            periodSeconds: 10
+            timeoutSeconds: 5
+            failureThreshold: 5
+          readinessProbe:
+            tcpSocket:
+              port: 8080
+            initialDelaySeconds: 60
+            periodSeconds: 10
+            timeoutSeconds: 5
+            failureThreshold: 3
\ No newline at end of file
diff --git a/mvnw b/mvnw
new file mode 100644
index 0000000..ba9212a
--- /dev/null
+++ b/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 "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000..d446e4d
--- /dev/null
+++ b/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 - jwtUser 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"
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..7ad80fc
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,131 @@
+<?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>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.1</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>com.nanyan.securitylink</groupId>
+    <artifactId>sl-gateway</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>sl-gateway</name>
+    <description>Security Link Gateway Project</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <spring-cloud.version>2021.0.5</spring-cloud.version>
+    </properties>
+
+    <dependencies>
+        <!-- Spring Cloud Gateway -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-gateway</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.83</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-config -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-config</artifactId>
+        </dependency>
+
+        <!-- Spring Cloud LoadBalancer -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+
+        <!-- Spring Boot Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-collections4</artifactId>
+            <version>4.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <version>2.17.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>2.17.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+            <version>2.17.0</version>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring-cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <!--		<pluginManagement>-->
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.7.1</version>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>com.nanyan.securitylink.GatewayApplication</mainClass>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <!--		</pluginManagement>-->
+
+    </build>
+</project>
\ No newline at end of file
diff --git a/service.yaml b/service.yaml
new file mode 100644
index 0000000..a839ebc
--- /dev/null
+++ b/service.yaml
@@ -0,0 +1,35 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  namespace: securitylink
+  name: sl-gateway
+spec:
+  ports:
+    - port: 8080
+      targetPort: 8080
+      protocol: TCP
+  type: NodePort
+  selector:
+    app: sl-gateway
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  namespace: securitylink
+  name: ingress-sl-gateway
+  annotations:
+    alb.ingress.kubernetes.io/scheme: internet-facing
+    alb.ingress.kubernetes.io/target-type: ip
+spec:
+  ingressClassName: alb
+  rules:
+    - http:
+        paths:
+          - path: /
+            pathType: Prefix
+            backend:
+              service:
+                name: sl-gateway
+                port:
+                  number: 8080
\ No newline at end of file
diff --git a/src/main/java/com/nanyan/securitylink/GatewayApplication.java b/src/main/java/com/nanyan/securitylink/GatewayApplication.java
new file mode 100644
index 0000000..b60414c
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/GatewayApplication.java
@@ -0,0 +1,11 @@
+package com.nanyan.securitylink;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class GatewayApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(GatewayApplication.class, args);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/nanyan/securitylink/common/Constant.java b/src/main/java/com/nanyan/securitylink/common/Constant.java
new file mode 100644
index 0000000..db84f13
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/common/Constant.java
@@ -0,0 +1,39 @@
+package com.nanyan.securitylink.common;
+
+public interface Constant {
+//    String USER_ID = "userId";
+    int picType = 1;
+    int textType = 0;
+    String ACCOUNT = "account";
+    int SUCCESS_STATUS = 200;
+    String WEATHER_GROUP = "weatherType";
+    String ADMIN_ROLE = "admin";
+    String MSG_LABEL_NAME = "MesTag";
+    int INVALID = 1;
+    int EFFECTIVE = 0;
+
+    int NOT_SENT = 0;
+    int SENT = 1;
+
+    String COUNTRY_CODE = "country";
+    String CRONTAB_CODE = "crontab";
+    String CITY_CODE = "city";
+    String UTC_ZONE_ID = "UTC";
+    String LAT = "lat";
+    String LON = "lon";
+    String LANGUAGE = "language";
+    String CN = "中文";
+    String CN_CODE = "zh_cn";
+    String EN = "英文";
+    String EN_CODE = "en";
+    String indexEmergencyCode="indexEmergency";
+    String indexCitySafeCode="indexCitySafe";
+    String indexFrendlinessCode="indexFrendliness";
+    String indexManmadeHazardsCode="indexManmadeHazards";
+    String indexNaturalHazardsCode="indexNaturalHazards";
+    String indexPublicHealthCode="indexPublicHealth";
+    Integer DISABLED = 1;
+    Integer DELETE = 3;
+    Integer NON_VIP = 1;
+    Integer VIP = 0;
+}
diff --git a/src/main/java/com/nanyan/securitylink/common/MsgCode.java b/src/main/java/com/nanyan/securitylink/common/MsgCode.java
new file mode 100644
index 0000000..8fcf958
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/common/MsgCode.java
@@ -0,0 +1,38 @@
+package com.nanyan.securitylink.common;
+
+public enum MsgCode {
+    SUCCESS(200, "success"),
+    FAILED(1001, "access failed"),
+    NOT_LOGIN(401, "not login"),
+    LOGIN_FAILED(402, "login failed"),
+    LOGIN_EXPIRE(403, "token expire")
+    //翻译异常
+    ,
+    TRANSLATE_ERROR(500, "translate error"),
+    //JSON字符串异常
+    JSON_ERROR(501, "json error")
+    ;
+    int code;
+    String msg;
+
+    MsgCode(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/config/FailoverProperties.java b/src/main/java/com/nanyan/securitylink/config/FailoverProperties.java
new file mode 100644
index 0000000..66b27c4
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/config/FailoverProperties.java
@@ -0,0 +1,29 @@
+package com.nanyan.securitylink.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import lombok.Data;
+import java.util.*;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "gateway.failover")
+public class FailoverProperties {
+    private List<TokenUriMapping> tokenMappings;
+
+    @Data
+    public static class TokenUriMapping {
+        private List<String> tokens;
+        private List<UriConfig> uriConfigs;
+    }
+
+    @Data
+    public static class UriConfig {
+        private String sourceUri;
+        private String targetUri;
+        private String primaryHost;
+        private String primaryUrl;
+        private String fallbackHost;
+        private String fallbackUrl;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/nanyan/securitylink/config/GatewayConfig.java b/src/main/java/com/nanyan/securitylink/config/GatewayConfig.java
new file mode 100644
index 0000000..a2a2694
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/config/GatewayConfig.java
@@ -0,0 +1,26 @@
+package com.nanyan.securitylink.config;
+
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import lombok.RequiredArgsConstructor;
+import com.nanyan.securitylink.filter.FailoverGatewayFilterFactory;
+
+@Configuration
+@RequiredArgsConstructor
+public class GatewayConfig {
+
+    private final FailoverGatewayFilterFactory failoverFilter;
+
+    @Bean
+    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
+        return builder.routes()
+                .route("token_based_route", r -> r
+                        .path("/**")
+                        .filters(f -> f
+                                .filter(failoverFilter.apply(new FailoverGatewayFilterFactory.Config())))
+                        .uri("no://op"))
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/nanyan/securitylink/config/RestTemplateConfig.java b/src/main/java/com/nanyan/securitylink/config/RestTemplateConfig.java
new file mode 100644
index 0000000..f5582b5
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/config/RestTemplateConfig.java
@@ -0,0 +1,13 @@
+package com.nanyan.securitylink.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestTemplateConfig {
+    @Bean
+    public RestTemplate restTemplate() {
+        return new RestTemplate();
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/config/SchedulerConfig.java b/src/main/java/com/nanyan/securitylink/config/SchedulerConfig.java
new file mode 100644
index 0000000..c394038
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/config/SchedulerConfig.java
@@ -0,0 +1,17 @@
+package com.nanyan.securitylink.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@Configuration
+public class SchedulerConfig {
+    @Bean
+    public ThreadPoolTaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+        scheduler.setPoolSize(10); // 设置线程池大小
+        scheduler.setThreadNamePrefix("task-scheduler-"); // 设置线程名前缀
+        scheduler.initialize();
+        return scheduler;
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/controller/HealthController.java b/src/main/java/com/nanyan/securitylink/controller/HealthController.java
new file mode 100644
index 0000000..60e69d9
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/controller/HealthController.java
@@ -0,0 +1,13 @@
+package com.nanyan.securitylink.controller;
+
+import com.nanyan.securitylink.vo.Response;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HealthController {
+    @GetMapping(value = "/health")
+    public Response<Boolean> checkHealth(){
+        return Response.SUCCESS(true);
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/dto/TranslateDTO.java b/src/main/java/com/nanyan/securitylink/dto/TranslateDTO.java
new file mode 100644
index 0000000..23cef11
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/dto/TranslateDTO.java
@@ -0,0 +1,10 @@
+package com.nanyan.securitylink.dto;
+
+import lombok.Data;
+
+@Data
+public class TranslateDTO {
+    TranslateInputDTO inputs;
+    String response_mode;
+    String user;
+}
diff --git a/src/main/java/com/nanyan/securitylink/dto/TranslateInputDTO.java b/src/main/java/com/nanyan/securitylink/dto/TranslateInputDTO.java
new file mode 100644
index 0000000..26e7576
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/dto/TranslateInputDTO.java
@@ -0,0 +1,9 @@
+package com.nanyan.securitylink.dto;
+
+import lombok.Data;
+
+@Data
+public class TranslateInputDTO {
+    String record_json;
+    String language;
+}
diff --git a/src/main/java/com/nanyan/securitylink/entity/ChatCompletionResponse.java b/src/main/java/com/nanyan/securitylink/entity/ChatCompletionResponse.java
new file mode 100644
index 0000000..4498140
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/entity/ChatCompletionResponse.java
@@ -0,0 +1,16 @@
+package com.nanyan.securitylink.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ChatCompletionResponse {
+    private String id;
+    private String object;
+    private long created;
+    private String model;
+    private List<Choice> choices;
+    private Usage usage;
+    private String systemFingerprint;
+}
diff --git a/src/main/java/com/nanyan/securitylink/entity/ChatInputData.java b/src/main/java/com/nanyan/securitylink/entity/ChatInputData.java
new file mode 100644
index 0000000..8888990
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/entity/ChatInputData.java
@@ -0,0 +1,12 @@
+package com.nanyan.securitylink.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ChatInputData {
+    private String model;
+    private Boolean stream = false;
+    private List<Message> messages;
+}
diff --git a/src/main/java/com/nanyan/securitylink/entity/Choice.java b/src/main/java/com/nanyan/securitylink/entity/Choice.java
new file mode 100644
index 0000000..f244bdc
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/entity/Choice.java
@@ -0,0 +1,11 @@
+package com.nanyan.securitylink.entity;
+
+import lombok.Data;
+
+@Data
+public class Choice {
+    private int index;
+    private Message message;
+    private Object logprobs; // logprobs is null in the JSON
+    private String finishReason;
+}
diff --git a/src/main/java/com/nanyan/securitylink/entity/Message.java b/src/main/java/com/nanyan/securitylink/entity/Message.java
new file mode 100644
index 0000000..52ac45b
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/entity/Message.java
@@ -0,0 +1,10 @@
+package com.nanyan.securitylink.entity;
+
+import lombok.Data;
+
+@Data
+public class Message {
+    private String role;
+    private String content;
+    
+}
diff --git a/src/main/java/com/nanyan/securitylink/entity/PromptTokensDetails.java b/src/main/java/com/nanyan/securitylink/entity/PromptTokensDetails.java
new file mode 100644
index 0000000..b9ac908
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/entity/PromptTokensDetails.java
@@ -0,0 +1,8 @@
+package com.nanyan.securitylink.entity;
+
+import lombok.Data;
+
+@Data
+public class PromptTokensDetails {
+    private int cachedTokens;
+}
diff --git a/src/main/java/com/nanyan/securitylink/entity/Usage.java b/src/main/java/com/nanyan/securitylink/entity/Usage.java
new file mode 100644
index 0000000..bac02f3
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/entity/Usage.java
@@ -0,0 +1,13 @@
+package com.nanyan.securitylink.entity;
+
+import lombok.Data;
+
+@Data
+public class Usage {
+    private int promptTokens;
+    private int completionTokens;
+    private int totalTokens;
+    private PromptTokensDetails promptTokensDetails;
+    private int promptCacheHitTokens;
+    private int promptCacheMissTokens;
+}
diff --git a/src/main/java/com/nanyan/securitylink/entity/UserHeader.java b/src/main/java/com/nanyan/securitylink/entity/UserHeader.java
new file mode 100644
index 0000000..aebdc94
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/entity/UserHeader.java
@@ -0,0 +1,40 @@
+package com.nanyan.securitylink.entity;
+
+public class UserHeader {
+    String userId;
+    String accountName;
+    String role;
+    String token;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getAccountName() {
+        return accountName;
+    }
+
+    public void setAccountName(String accountName) {
+        this.accountName = accountName;
+    }
+
+    public String getRole() {
+        return role;
+    }
+
+    public void setRole(String role) {
+        this.role = role;
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/entity/UserThreadLocal.java b/src/main/java/com/nanyan/securitylink/entity/UserThreadLocal.java
new file mode 100644
index 0000000..165af52
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/entity/UserThreadLocal.java
@@ -0,0 +1,14 @@
+package com.nanyan.securitylink.entity;
+
+public class UserThreadLocal {
+    private static final ThreadLocal<UserHeader> userHeaderThreadLocal = new ThreadLocal<>();
+    public static void set(UserHeader userHeader){
+        userHeaderThreadLocal.set(userHeader);
+    }
+    public static UserHeader get(){
+        return userHeaderThreadLocal.get();
+    }
+    public static void remove(){
+        userHeaderThreadLocal.remove();
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/execption/BaseException.java b/src/main/java/com/nanyan/securitylink/execption/BaseException.java
new file mode 100644
index 0000000..4910eaa
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/execption/BaseException.java
@@ -0,0 +1,20 @@
+package com.nanyan.securitylink.execption;
+
+import com.nanyan.securitylink.common.MsgCode;
+
+public class BaseException extends RuntimeException {
+    private int code;
+
+    public BaseException(MsgCode msgCode) {
+        super(msgCode.getMsg());
+        this.code = msgCode.getCode();
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/execption/HeaderException.java b/src/main/java/com/nanyan/securitylink/execption/HeaderException.java
new file mode 100644
index 0000000..2dbb83d
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/execption/HeaderException.java
@@ -0,0 +1,9 @@
+package com.nanyan.securitylink.execption;
+
+import com.nanyan.securitylink.common.MsgCode;
+
+public class HeaderException extends BaseException {
+    public HeaderException(MsgCode msgCode) {
+        super(msgCode);
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/filter/FailoverGatewayFilterFactory.java b/src/main/java/com/nanyan/securitylink/filter/FailoverGatewayFilterFactory.java
new file mode 100644
index 0000000..85347ed
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/filter/FailoverGatewayFilterFactory.java
@@ -0,0 +1,208 @@
+package com.nanyan.securitylink.filter;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.nanyan.securitylink.service.TokenRouteMappingService;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+@Component
+public class FailoverGatewayFilterFactory extends AbstractGatewayFilterFactory<FailoverGatewayFilterFactory.Config> {
+
+    private static final ThreadLocal<String> REQUEST_BODY_CACHE = new ThreadLocal<>();
+    private final WebClient.Builder webClientBuilder;
+    private final TokenRouteMappingService tokenRouteMappingService;
+
+    public FailoverGatewayFilterFactory(WebClient.Builder webClientBuilder,
+                                        TokenRouteMappingService tokenRouteMappingService) {
+        super(Config.class);
+        this.webClientBuilder = webClientBuilder;
+        this.tokenRouteMappingService = tokenRouteMappingService;
+    }
+
+    @Override
+    public GatewayFilter apply(Config config) {
+        return (exchange, chain) -> {
+            try {
+                String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+                String token = authHeader != null && authHeader.startsWith("Bearer ")
+                        ? authHeader.substring(7)
+                        : null;
+                String sourceUri = exchange.getRequest().getPath().value();
+
+                if (token != null) {
+                    return tokenRouteMappingService.findMatchingUriConfig(token, sourceUri)
+                            .map(uriConfig -> {
+                                return cacheRequestBody(exchange).flatMap(cachedBody -> {
+                                    log.info("Found matching URI config for token: {}, sourceUri: {}", token, sourceUri);
+                                    return tryRequest(
+                                            uriConfig.getPrimaryUrl(),
+                                            sourceUri,
+                                            uriConfig.getPrimaryHost(),
+                                            exchange,
+                                            true
+                                    ).onErrorResume(primaryError -> {
+                                        log.error("Primary endpoint failed: {}, error: {}",
+                                                uriConfig.getPrimaryUrl() + sourceUri,
+                                                primaryError.getMessage());
+
+                                        String targetUri = tokenRouteMappingService.resolveTargetUri(sourceUri, uriConfig);
+                                        return tryRequest(
+                                                uriConfig.getFallbackUrl(),
+                                                targetUri,
+                                                uriConfig.getFallbackHost(),
+                                                exchange,
+                                                false
+                                        );
+                                    });
+                                });
+                            })
+                            .orElse(chain.filter(exchange));
+                }
+
+                return chain.filter(exchange);
+            } finally {
+                // 确保在请求处理完成后清理 ThreadLocal
+                REQUEST_BODY_CACHE.remove();
+            }
+        };
+    }
+
+    private Mono<String> cacheRequestBody(ServerWebExchange exchange) {
+
+        ServerHttpRequest request = exchange.getRequest();
+        if (!requiresRequestBody(request.getMethod())) {
+            return Mono.just("");
+        }
+
+        // 读取并缓存请求体
+        return DataBufferUtils.join(request.getBody())
+                .map(dataBuffer -> {
+                    try {
+                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
+                        dataBuffer.read(bytes);
+                        String body = new String(bytes, StandardCharsets.UTF_8);
+                        // 将请求体存储在 ThreadLocal 中
+                        REQUEST_BODY_CACHE.set(body);
+                        return body;
+                    } finally {
+                        DataBufferUtils.release(dataBuffer);
+                    }
+                })
+                .defaultIfEmpty("");
+    }
+
+    private Mono<Void> tryRequest(String baseUrl, String uri, String host,
+                                  ServerWebExchange exchange, boolean isSourceRequest) {
+        String cachedBody = REQUEST_BODY_CACHE.get();
+        ServerHttpRequest request = exchange.getRequest();
+        String fullUrl = baseUrl + (baseUrl.endsWith("/") ? uri.substring(1) : uri);
+
+        log.info("Trying request to: {}", fullUrl);
+        log.info("Using cached request body: {}", cachedBody);
+
+        WebClient.RequestBodySpec requestBodySpec = webClientBuilder.build()
+                .method(request.getMethod())
+                .uri(fullUrl + (request.getQueryParams().isEmpty() ? "" :
+                        "?" + request.getQueryParams().toString().substring(1)));
+
+        // 复制请求头
+        requestBodySpec.headers(headers -> {
+            headers.addAll(request.getHeaders());
+            headers.set(HttpHeaders.HOST, host);
+        });
+
+        // 设置请求体
+        if (requiresRequestBody(request.getMethod()) && cachedBody != null && !cachedBody.isEmpty()) {
+            MediaType contentType = request.getHeaders().getContentType();
+            if (contentType != null) {
+                requestBodySpec.contentType(contentType);
+            }
+            requestBodySpec.body(BodyInserters.fromValue(cachedBody));
+        }
+
+        return requestBodySpec
+                .exchange()
+                .flatMap(clientResponse -> handleResponse(exchange, clientResponse, isSourceRequest));
+    }
+
+    private Mono<Void> handleResponse(ServerWebExchange exchange,
+                                      org.springframework.web.reactive.function.client.ClientResponse clientResponse,
+                                      boolean isSourceRequest) {
+        HttpStatus responseStatus = clientResponse.statusCode();
+
+        if (isSourceRequest) {
+            return clientResponse.bodyToMono(String.class)
+                    .flatMap(body -> {
+                        log.info("Source request response: status={}, body={}", responseStatus, body);
+
+                        if (responseStatus != HttpStatus.OK) {
+                            return Mono.error(new RuntimeException(
+                                    String.format("Source request HTTP status not 200: %s, body: %s",
+                                            responseStatus.value(), body)));
+                        }
+
+                        try {
+                            JSONObject jsonBody = JSON.parseObject(body);
+                            Integer bodyStatus = jsonBody.getInteger("status");
+                            if (bodyStatus == null || bodyStatus != 200) {
+                                return Mono.error(new RuntimeException(
+                                        String.format("Source request body status not 200: %s, body: %s",
+                                                bodyStatus, body)));
+                            }
+
+                            exchange.getResponse().setStatusCode(responseStatus);
+                            exchange.getResponse().getHeaders().putAll(clientResponse.headers().asHttpHeaders());
+                            DataBuffer buffer = exchange.getResponse().bufferFactory()
+                                    .wrap(body.getBytes(StandardCharsets.UTF_8));
+                            return exchange.getResponse().writeWith(Mono.just(buffer));
+
+                        } catch (Exception e) {
+                            log.error("Error parsing response body: {}", e.getMessage());
+                            return Mono.error(new RuntimeException(
+                                    String.format("Failed to parse response body as JSON: %s", body)));
+                        }
+                    });
+        }
+
+        exchange.getResponse().setStatusCode(responseStatus);
+        exchange.getResponse().getHeaders().putAll(clientResponse.headers().asHttpHeaders());
+
+        return clientResponse.bodyToMono(DataBuffer.class)
+                .flatMap(buffer -> exchange.getResponse().writeWith(Mono.just(buffer)))
+                .onErrorResume(throwable -> {
+                    log.info("Error handling response: {}", throwable.getMessage());
+                    exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
+                    String errorMessage = throwable.getMessage();
+                    DataBuffer buffer = exchange.getResponse().bufferFactory()
+                            .wrap(errorMessage.getBytes(StandardCharsets.UTF_8));
+                    return exchange.getResponse().writeWith(Mono.just(buffer));
+                });
+    }
+
+    private boolean requiresRequestBody(HttpMethod method) {
+        return method == HttpMethod.POST ||
+                method == HttpMethod.PUT ||
+                method == HttpMethod.PATCH;
+    }
+
+    @Data
+    public static class Config {
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/nanyan/securitylink/schedule/RefreshConfigSync.java b/src/main/java/com/nanyan/securitylink/schedule/RefreshConfigSync.java
new file mode 100644
index 0000000..1a742d6
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/schedule/RefreshConfigSync.java
@@ -0,0 +1,39 @@
+package com.nanyan.securitylink.schedule;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+@Slf4j
+@Component
+public class RefreshConfigSync {
+    @Autowired
+    RestTemplate restTemplate;
+    public void autoRefreshConfig(){
+        try{
+            String url = "http://127.0.0.1:8080/actuator/refresh";
+            HttpEntity header = getHeader();
+            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, header, String.class);
+            if(response.getStatusCode().is2xxSuccessful()){
+                log.info("配置刷新成功:{}", response.getBody());
+            }
+        }catch (Exception e){
+            log.error("", e);
+        }
+
+    }
+
+    private HttpEntity getHeader(){
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.set("Content-Type","application/json");
+        return new HttpEntity<>(httpHeaders);
+    }
+
+
+
+}
diff --git a/src/main/java/com/nanyan/securitylink/schedule/ScheduledTasksService.java b/src/main/java/com/nanyan/securitylink/schedule/ScheduledTasksService.java
new file mode 100644
index 0000000..6dd7598
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/schedule/ScheduledTasksService.java
@@ -0,0 +1,30 @@
+package com.nanyan.securitylink.schedule;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+
+@Service
+@EnableScheduling
+public class ScheduledTasksService {
+    private final TaskScheduler taskScheduler;
+
+    @Autowired
+    RefreshConfigSync refreshConfigSync;
+
+    public ScheduledTasksService(TaskScheduler taskScheduler) {
+        this.taskScheduler = taskScheduler;
+    }
+
+    @PostConstruct
+    public void scheduleTask() {
+
+        //定时刷新配置
+        taskScheduler.scheduleWithFixedDelay(() -> {
+            refreshConfigSync.autoRefreshConfig();
+        }, 60000);
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/service/TokenRouteMappingService.java b/src/main/java/com/nanyan/securitylink/service/TokenRouteMappingService.java
new file mode 100644
index 0000000..bea9b5b
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/service/TokenRouteMappingService.java
@@ -0,0 +1,34 @@
+package com.nanyan.securitylink.service;
+
+import com.nanyan.securitylink.config.FailoverProperties;
+import com.nanyan.securitylink.config.FailoverProperties.UriConfig;
+import org.springframework.stereotype.Service;
+import lombok.RequiredArgsConstructor;
+import org.springframework.util.AntPathMatcher;
+import java.util.Optional;
+
+@Service
+@RequiredArgsConstructor
+public class TokenRouteMappingService {
+
+    private final FailoverProperties failoverProperties;
+    private final AntPathMatcher pathMatcher = new AntPathMatcher();
+
+    public Optional<UriConfig> findMatchingUriConfig(String token, String sourceUri) {
+        Optional<UriConfig> first = failoverProperties.getTokenMappings().stream()
+                .filter(mapping -> mapping.getTokens().contains(token))
+                .flatMap(mapping -> mapping.getUriConfigs().stream())
+                .filter(uriConfig -> pathMatcher.match(uriConfig.getSourceUri(), sourceUri))
+                .findFirst();
+        return first;
+    }
+
+    public String resolveTargetUri(String sourceUri, UriConfig uriConfig) {
+        if (uriConfig.getSourceUri().contains("*")) {
+            String remaining = pathMatcher.extractPathWithinPattern(
+                    uriConfig.getSourceUri(), sourceUri);
+            return uriConfig.getTargetUri().replace("{remaining}", remaining);
+        }
+        return uriConfig.getTargetUri();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/nanyan/securitylink/utils/DateUtils.java b/src/main/java/com/nanyan/securitylink/utils/DateUtils.java
new file mode 100644
index 0000000..342c2ec
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/utils/DateUtils.java
@@ -0,0 +1,283 @@
+package com.nanyan.securitylink.utils;
+
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.util.*;
+
+@Slf4j
+public class DateUtils {
+    public static long dayTimeStamp(Date date) throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+        String formattedDate = sdf.format(date);
+        return sdf.parse(formattedDate).getTime();
+    }
+    public static boolean isValidDate(Date date) {
+        if (date == null) {
+            return false; // Date 为 null,非法
+        }
+
+        // 检查 Date 是否在合理范围内
+        // 例如,检查是否大于某个最小日期(如 1970 年 1 月 1 日)
+        Date minDate = new Date(0); // 1970-01-01 00:00:00 UTC
+        if (date.before(minDate)) {
+            return false; // Date 早于 1970 年 1 月 1 日,非法
+        }
+
+        // 检查是否小于某个最大日期(如 2100 年 1 月 1 日)
+        Date maxDate = new Date(4102444800000L); // 2100-01-01 00:00:00 UTC
+        if (date.after(maxDate)) {
+            return false; // Date 晚于 2100 年 1 月 1 日,非法
+        }
+
+        return true; // Date 合法
+    }
+
+    public static long LocalDateTimeToMillis(LocalDateTime localDateTime, String zoneId) {
+        // 结合时区,这里使用系统默认时区
+        ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(zoneId));
+        // 转换成Instant
+        Instant instant = zonedDateTime.toInstant();
+        // 获取毫秒值
+        return instant.toEpochMilli();
+    }
+
+    public static Long currentUTCMilli(){
+        // 获取当前UTC时间的Instant对象
+        Instant now = Instant.now();
+
+        // 获取秒级时间戳
+        long epochMilli = now.toEpochMilli();
+        return epochMilli;
+    }
+
+    public static long currentMonthlyStart(Date start) {
+        LocalDateTime now = start.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
+        // 将开始和结束时间转换为毫秒级时间戳
+        return firstDayOfMonth.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+    }
+    public static long currentMonthlyEnd(Date start){
+        LocalDateTime now = start.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime firstDayOfNextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
+        // 将开始和结束时间转换为毫秒级时间戳
+        return firstDayOfNextMonth.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+    }
+
+    public static long yearTimeStamp(Date date) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
+        String year = sdf.format(date);
+        return toYMD(String.format("%s-01-01",year)).toInstant().toEpochMilli();
+    }
+
+    public static Long monthTimeStamp(Date date) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
+        String yyyyMM = sdf.format(date);
+        return toYMD(String.format("%s-01",yyyyMM)).toInstant().toEpochMilli();
+    }
+    public static long quarterTimeStamp(Date date) {
+        int month = getMonth(date);
+        String monthStr = null;
+        int year = getYear(date);
+        if (month <= 3) {
+            monthStr = "01";
+        } else if (month <= 6) {
+            monthStr = "03";
+        } else if (month <= 9) {
+            monthStr = "06";
+        } else {
+            monthStr = "09";
+        }
+        String format = String.format("%d-%s-01", year, monthStr);
+        return toYMD(format).getTime();
+    }
+
+    public static String dateYYYYMMDD(Date date){
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        String formattedDate = sdf.format(date);
+        return formattedDate;
+    }
+
+    public static String getHourStr(Long dt ){
+        Date date = new Date(dt * 1000);
+        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
+        return sdf.format(date);
+    }
+
+    public static Integer getHour(Date dt ){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(dt);
+
+        // 获取小时数
+        return calendar.get(Calendar.HOUR_OF_DAY);
+    }
+
+    public static Date toYMD(String dateStr) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        try{
+            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+            return sdf.parse(dateStr);
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static Date toYMD(Date date) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        try{
+            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+            String format = sdf.format(date);
+            return sdf.parse(format);
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static Date lastDate(Date date, Integer day){
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        calendar.setTime(date);
+        calendar.add(Calendar.DAY_OF_MONTH, day);
+        return calendar.getTime();
+    }
+
+    public static int getMonth(Date date){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        int month = calendar.get(Calendar.MONTH) + 1; // 由于月份从0开始,所以需要+1
+        return month;
+    }
+    public static int getYear(Date date){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        int year = calendar.get(Calendar.YEAR);
+        return year;
+    }
+    public static String getDaily(Date date){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        int month = calendar.get(Calendar.MONTH) + 1; // 由于月份从0开始,所以需要+1
+        int day = calendar.get(Calendar.DAY_OF_MONTH) + 1; // 由于月份从0开始,所以需要+1
+        return String.format("%d-%d", month, day);
+    }
+
+    public static String calculateTimeDifference(Date date) {
+        if(date == null){
+            return "";
+        }
+        Date currentDate = new Date();
+        long differenceInMillis = currentDate.getTime() - date.getTime();
+        long seconds = differenceInMillis / 1000;
+
+        if (seconds < 60) {
+            return seconds + "秒之前";
+        } else if (seconds < 3600) {
+            long minutes = seconds / 60;
+            return minutes + "分钟之前";
+        } else if(seconds < 3600 * 24){
+            long hours = seconds / 3600;
+            return hours + "小时之前";
+        }else {
+            long hours = seconds / (3600*24);
+            return hours + "天之前";
+        }
+    }
+    /**
+     * 将毫秒时间长度转换为 N 天 N 小时 N 分 N 秒 格式
+     * @param millis 毫秒数
+     * @return 格式化后的时间字符串
+     */
+    public static String formatMillis(long millis) {
+        // 计算总秒数
+        long seconds = millis / 1000;
+        // 计算天数
+        long days = seconds / (24 * 60 * 60);
+        seconds %= (24 * 60 * 60);
+        // 计算小时数
+        long hours = seconds / (60 * 60);
+        seconds %= (60 * 60);
+        // 计算分钟数
+        long minutes = seconds / 60;
+        // 计算剩余秒数
+        seconds %= 60;
+
+        StringBuilder result = new StringBuilder();
+        if (days > 0) {
+            result.append(days).append("天 ");
+        }
+        if (hours > 0) {
+            result.append(hours).append("小时 ");
+        }
+        if (minutes > 0) {
+            result.append(minutes).append("分 ");
+        }
+        if (seconds > 0) {
+            result.append(seconds).append("ç§’");
+        }
+        // 如果结果为空,说明时间为 0,返回 "0 秒"
+        if (result.length() == 0) {
+            result.append("0 ç§’");
+        }
+
+        return result.toString();
+    }
+
+    public static String getWeekFormat(Date date) {
+        SimpleDateFormat sdf = new SimpleDateFormat("EEEE", Locale.SIMPLIFIED_CHINESE);
+        String format = sdf.format(date);
+        if("星期一".equals(format)){
+            return "周一";
+        }else  if("星期二".equals(format)){
+            return "周二";
+        }else  if("星期三".equals(format)){
+            return "周三";
+        }else  if("星期四".equals(format)){
+            return "周四";
+        }else  if("星期五".equals(format)){
+            return "周五";
+        }else  if("星期六".equals(format)){
+            return "周六";
+        }else  if("星期日".equals(format)){
+            return "周日";
+        }
+        return format;
+    }
+
+
+    public static String getYYDDFormat(Date date) {
+        SimpleDateFormat sdf = new SimpleDateFormat("M月d日");
+        return sdf.format(date);
+    }
+    public static long getYesterday0H(){
+        // 获取当前时间
+        LocalDateTime now = LocalDateTime.now();
+        // 计算昨天的时间
+        LocalDateTime yesterday = now.minusDays(1);
+        // 设置为昨天 0 点
+        LocalDateTime yesterdayNoon = yesterday.withHour(0).withMinute(0).withSecond(0).withNano(0);
+        return yesterdayNoon.toInstant(ZoneOffset.UTC).getEpochSecond();
+    }
+
+    public static long getYesterday12H(){
+        // 获取当前时间
+        LocalDateTime now = LocalDateTime.now();
+        // 计算昨天的时间
+        LocalDateTime yesterday = now.minusDays(1);
+        // 设置为昨天 12 点
+        LocalDateTime yesterdayNoon = yesterday.withHour(12).withMinute(0).withSecond(0).withNano(0);
+        return yesterdayNoon.toInstant(ZoneOffset.UTC).getEpochSecond();
+    }
+
+    public static int dateDifference(Date start, Date end) {
+        LocalDate s = start.toInstant().atZone(ZoneId.of("UTC")).toLocalDate();
+        LocalDate e = end.toInstant().atZone(ZoneId.of("UTC")).toLocalDate();
+        return (int)ChronoUnit.DAYS.between(s, e);
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/vo/AIResponse.java b/src/main/java/com/nanyan/securitylink/vo/AIResponse.java
new file mode 100644
index 0000000..f388e6e
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/vo/AIResponse.java
@@ -0,0 +1,15 @@
+package com.nanyan.securitylink.vo;
+
+import lombok.Data;
+
+@Data
+public class AIResponse {
+    private String event;
+    private String task_id;
+    private String id;
+    private String message_id;
+    private String mode;
+    private String answer;
+    private ResultVO outputs;
+    private long created_at;
+}
diff --git a/src/main/java/com/nanyan/securitylink/vo/CodeVO.java b/src/main/java/com/nanyan/securitylink/vo/CodeVO.java
new file mode 100644
index 0000000..370ba89
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/vo/CodeVO.java
@@ -0,0 +1,9 @@
+package com.nanyan.securitylink.vo;
+
+import lombok.Data;
+
+@Data
+public class CodeVO {
+    String code;
+    String name;
+}
diff --git a/src/main/java/com/nanyan/securitylink/vo/Response.java b/src/main/java/com/nanyan/securitylink/vo/Response.java
new file mode 100644
index 0000000..eeb7a60
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/vo/Response.java
@@ -0,0 +1,61 @@
+package com.nanyan.securitylink.vo;
+
+import com.nanyan.securitylink.common.MsgCode;
+
+public class Response<T> {
+    String msg;
+    int code;
+    T data;
+
+    public static Response getResponse(MsgCode msgCode){
+        Response response = new Response();
+        response.setCode(msgCode.getCode());
+        response.setMsg(msgCode.getMsg());
+        return response;
+    }
+
+    public static Response getResponse(int code, String msg){
+        Response response = new Response();
+        response.setCode(code);
+        response.setMsg(msg);
+        return response;
+    }
+
+    public static<T> Response<T> SUCCESS(T data){
+        Response response = new Response<T>();
+        response.setCode(MsgCode.SUCCESS.getCode());
+        response.setMsg(MsgCode.SUCCESS.getMsg());
+        response.setData(data);
+        return response;
+    }
+    public static<T> Response<T> FAILED(T data){
+        Response response = new Response();
+        response.setCode(MsgCode.FAILED.getCode());
+        response.setMsg(MsgCode.FAILED.getMsg());
+        response.setData(data);
+        return response;
+    }
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+}
diff --git a/src/main/java/com/nanyan/securitylink/vo/ResultVO.java b/src/main/java/com/nanyan/securitylink/vo/ResultVO.java
new file mode 100644
index 0000000..179cd82
--- /dev/null
+++ b/src/main/java/com/nanyan/securitylink/vo/ResultVO.java
@@ -0,0 +1,10 @@
+package com.nanyan.securitylink.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ResultVO {
+    List<CodeVO> result;
+}
diff --git a/src/main/resources/application-prd.properties b/src/main/resources/application-prd.properties
new file mode 100644
index 0000000..123755f
--- /dev/null
+++ b/src/main/resources/application-prd.properties
@@ -0,0 +1,6 @@
+spring.cloud.config.name=sl-gateway
+spring.cloud.config.profile=prd
+spring.cloud.config.label=master
+spring.cloud.config.enabled=true
+#spring.config.import=optional:configserver:http://sl-config-center:8080
+#spring.config.import=optional:configserver:http://43.199.200.152:32594
\ No newline at end of file
diff --git a/src/main/resources/application-qa.properties b/src/main/resources/application-qa.properties
new file mode 100644
index 0000000..dddfbca
--- /dev/null
+++ b/src/main/resources/application-qa.properties
@@ -0,0 +1,26 @@
+spring.cloud.config.name=sl-gateway
+spring.cloud.config.profile=qa
+spring.cloud.config.label=master
+spring.cloud.config.enabled=true
+#spring.config.import=optional:configserver:http://sl-config-center:8080
+spring.config.import=optional:configserver:http://43.199.200.152:32594
+#crime.mapping.event={"Arson":"\u7EB5\u706B\u7F6A","Assault":"\u88AD\u51FB/\u653B\u51FB","Burglary":"\u5165\u5BA4\u76D7\u7A83","Disturbing the Peace":"\u6270\u4E71\u516C\u5171\u79E9\u5E8F","Drugs / Alcohol Violations":"\u6BD2\u54C1/\u9152\u7CBE\u8FDD\u89C4","DUI":"\u9189\u9A7E","Fraud":"\u8BC8\u9A97","Homicide":"\u6740\u4EBA\u7F6A","Motor Vehicle Theft":"\u673A\u52A8\u8F66\u76D7\u7A83","Robbery":"\u62A2\u52AB","Sex Crimes":"\u6027\u72AF\u7F6A","Theft / Larceny":"\u76D7\u7A83","Vandalism":"\u6076\u610F\u7834\u574F\u8D22\u7269","Vehicle Break-In / Theft":"\u8F66\u8F86\u95EF\u5165/\u76D7\u7A83","Weapons":"\u6D89\u62A2\u6D89\u68B0\u72AF\u7F6A"}
+# Gateway Configuration
+#spring.cloud.gateway.httpclient.connect-timeout=5000
+#spring.cloud.gateway.httpclient.response-timeout=5000
+#
+## Gateway Failover Configuration
+#gateway.failover.token-mappings[0].tokens[0]=app-KNq0O8kENP4ITqSmqHQ0IzAt1
+#gateway.failover.token-mappings[0].uri-configs[0].source-uri=/v1/workflows/run
+#gateway.failover.token-mappings[0].uri-configs[0].target-uri=/api/v1/tag
+#gateway.failover.token-mappings[0].uri-configs[0].primary-host=18.163.46.22
+#gateway.failover.token-mappings[0].uri-configs[0].primary-url=http://18.163.46.22
+#gateway.failover.token-mappings[0].uri-configs[0].fallback-host=127.0.0.1
+#gateway.failover.token-mappings[0].uri-configs[0].fallback-url=http://127.0.0.1:8081
+
+#gateway.failover.token-mappings[0].uri-configs[1].source-uri=/v1/workflows/status/*
+#gateway.failover.token-mappings[0].uri-configs[1].target-uri=/api/v1/status/{remaining}
+#gateway.failover.token-mappings[0].uri-configs[1].primary-host=18.163.46.22
+#gateway.failover.token-mappings[0].uri-configs[1].primary-url=http://18.163.46.22
+#gateway.failover.token-mappings[0].uri-configs[1].fallback-host=k8s-security-ingresss-2004545575-1912502751.ap-east-1.elb.amazonaws.com
+#gateway.failover.token-mappings[0].uri-configs[1].fallback-url=http://k8s-security-ingresss-2004545575-1912502751.ap-east-1.elb.amazonaws.com
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..f0fea53
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,7 @@
+spring.application.name=sl-gateway
+spring.profiles.active=qa
+#server.servlet.context-path=/api/v1
+pagehelper.reasonable=true
+pagehelper.support-methods-arguments=true
+management.endpoints.web.exposure.include=refresh
+#logging.config=classpath:log4j2.xml
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..b4a14ef
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,87 @@
+<Configuration status="WARN" monitorInterval="1800">
+
+    <!--变量配置-->
+    <Properties>
+        <!--应用名称-->
+        <property name="APP_NAME">sl-gateway</property>
+        <!--日志存放路径-->
+        <property name="LOG_PATH">./logs/${APP_NAME}</property>
+        <!--日志备份路径-->
+        <property name="LOG_BACKUP_PATH">${LOG_PATH}/backup</property>
+        <!--日志输出格式-控制台-->
+        <property name="PATTERN_CONSOLE">%d{yyyy-MM-dd HH:mm:ss.SSS} | %blue{%traceId} | %highlight{%-5p} | %magenta{${sys:PID}} | %yellow{%t} | %cyan{%l} : %msg%n</property>
+        <!--日志输出格式-文件-->
+        <property name="PATTERN_FILE">%d{yyyy-MM-dd HH:mm:ss.SSS} | %traceId | %-5p | ${sys:PID} | %t | %l : %msg%n</property>
+    </Properties>
+
+    <!--定义日志输出目的地,内容和格式等-->
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout pattern="${PATTERN_CONSOLE}"/>
+        </Console>
+
+        <!--可归档文件
+            1. fileName: 日志存储路径
+            2. filePattern: 历史日志封存路径。其中%d{yyyy-MM-dd}表示了日志的时间单位是天,log4j2自动识别zip等后缀,表示历史日志需要压缩
+        -->
+        <RollingFile name="RollingFile" fileName="${LOG_PATH}/${APP_NAME}.log" filePattern="${LOG_BACKUP_PATH}/$${date:yyyy-MM}/${APP_NAME}-%d{yyyy-MM-dd}_%i.log.zip">
+            <!--输出日志的格式, 不设置默认为:%m%n-->
+            <PatternLayout pattern="${PATTERN_FILE}"/>
+            <!--只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
+            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
+
+            <!--归档设置-->
+            <Policies>
+                <!--按时间间隔归档:
+                    1. interval=时间间隔, 单位由filePattern的%d日期格式指定, 此处配置代表每一天归档一次
+                    2. modulate="true" 是否对interval取模,决定了下一次触发的时间点
+                -->
+                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
+                <!-- 按照日志文件的大小: size表示当前日志文件的最大size,支持单位:KB/MB/GB-->
+                <SizeBasedTriggeringPolicy size="50MB"/>
+            </Policies>
+            <!-- 历史日志配置: 该属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
+            <DefaultRolloverStrategy max="30"/>
+        </RollingFile>
+
+        <!--错误信息单独归档-->
+        <RollingFile name="RollingFileError" fileName="${LOG_PATH}/${APP_NAME}-error.log" filePattern="${LOG_BACKUP_PATH}/$${date:yyyy-MM}/${APP_NAME}-error-%d{yyyy-MM-dd}_%i.log.zip">
+            <PatternLayout pattern="${PATTERN_FILE}"/>
+            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy/>
+                <SizeBasedTriggeringPolicy size="50MB"/>
+            </Policies>
+        </RollingFile>
+    </Appenders>
+
+
+    <!--Loggers配置-->
+    <Loggers>
+
+        <!--
+        注意点:
+        1. logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等:
+           (1). name: 用来指定该logger所适用的类或者类所在的包全路径,继承自Root节点.
+           (2). AppenderRef:关联的Appender, 只有定义了logger并引入的appender,appender才会生效
+           (3). additivity: logEvent的传递性。true LogEvent处理后传递给父Logger打印。false LogEvent处理后不再向上传递给父Logger(解决日志重复输出问题)
+           (4). logger配置的level必须高于或等于Appenders中ThresholdFilter配置的过滤level, 否则会造成信息丢失
+        2. root配置日志的根节点
+        -->
+
+        <!-- 同步日志配置-->
+        <logger name="com.sky.hello.mapper" level="debug" additivity="false">
+            <AppenderRef ref="Console"/>
+            <AppenderRef ref="RollingFile"/>
+            <AppenderRef ref="RollingFileError"/>
+        </logger>
+
+        <root level="info">
+            <AppenderRef ref="Console"/>
+            <AppenderRef ref="RollingFile"/>
+            <AppenderRef ref="RollingFileError"/>
+        </root>
+
+    </Loggers>
+
+</Configuration>
\ No newline at end of file
-- 
2.22.0