Compare commits
2 Commits
8a27fe5fa9
...
a58f9fd50e
| Author | SHA1 | Date | |
|---|---|---|---|
| a58f9fd50e | |||
| cd4c0125a1 |
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/mvnw text eol=lf
|
||||
*.cmd text eol=crlf
|
||||
51
.gitignore
vendored
51
.gitignore
vendored
@ -1,26 +1,33 @@
|
||||
# ---> Java
|
||||
# Compiled class file
|
||||
*.class
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
19
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
19
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
wrapperVersion=3.3.2
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
|
||||
259
mvnw
vendored
Normal file
259
mvnw
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
#!/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
|
||||
#
|
||||
# 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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.3.2
|
||||
#
|
||||
# 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
|
||||
}
|
||||
|
||||
trim() {
|
||||
# MWRAPPER-139:
|
||||
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||
# Needed for removing poorly interpreted newline sequences when running in more
|
||||
# exotic environments such as mingw bash on Windows.
|
||||
printf "%s" "${1}" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||
while IFS="=" read -r key value; do
|
||||
case "${key-}" in
|
||||
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||
distributionSha256Sum) distributionSha256Sum=$(trim "${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_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||
MAVEN_HOME="${MAVEN_USER_HOME}/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 "$@"
|
||||
149
mvnw.cmd
vendored
Normal file
149
mvnw.cmd
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
<# : 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 http://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.2
|
||||
@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"
|
||||
if ($env:MAVEN_USER_HOME) {
|
||||
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/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"
|
||||
101
pom.xml
Normal file
101
pom.xml
Normal file
@ -0,0 +1,101 @@
|
||||
<?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>3.4.3</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.dongni</groupId>
|
||||
<artifactId>CollisionAvoidance</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>CollisionAvoidance</name>
|
||||
<description>CollisionAvoidance</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
<version>3.0.12</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Kafka -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MongoDB依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson依赖(用于Redis序列化) -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,16 @@
|
||||
package com.dongni.collisionavoidance;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
|
||||
|
||||
@EnableScheduling
|
||||
@SpringBootApplication
|
||||
@EnableMongoRepositories
|
||||
public class CollisionAvoidanceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CollisionAvoidanceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.dongni.collisionavoidance.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
/**
|
||||
* 自定义线程池,避免定时任务单线程阻塞的情况
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync // 启用异步支持
|
||||
public class SchedulerConfig {
|
||||
|
||||
@Bean
|
||||
public ThreadPoolTaskScheduler taskScheduler() {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
// 设置线程池大小,根据需求调整
|
||||
scheduler.setPoolSize(3);
|
||||
// 设置线程名称前缀
|
||||
scheduler.setThreadNamePrefix("ScheduledTask-");
|
||||
return scheduler;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 航空器实体类
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Aircraft extends MovingObject{
|
||||
/**
|
||||
* 航班号
|
||||
*/
|
||||
@NotBlank(message = "航班号不能为空")
|
||||
private String flightNo;
|
||||
|
||||
/**
|
||||
* 航迹号
|
||||
*/
|
||||
private Long trackNumber;
|
||||
|
||||
@JsonCreator
|
||||
public Aircraft(
|
||||
@JsonProperty("latitude") double latitude,
|
||||
@JsonProperty("longitude") double longitude,
|
||||
@JsonProperty("altitude") double altitude,
|
||||
@JsonProperty("time") long timestamp
|
||||
) {
|
||||
this.currentPosition = new GeoPosition(latitude, longitude, altitude);
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor // 必须有无参构造函数
|
||||
@AllArgsConstructor
|
||||
public class GeoPosition {
|
||||
// 纬度(度)
|
||||
@JsonProperty("latitude")
|
||||
public double latitude;
|
||||
// 经度(度)
|
||||
@JsonProperty("longitude")
|
||||
public double longitude;
|
||||
// 高度(米)
|
||||
@JsonProperty("altitude")
|
||||
public double altitude;
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MovementState {
|
||||
public GeoPosition position;
|
||||
//速度
|
||||
public Velocity velocity;
|
||||
// 航向(度)
|
||||
public double heading;
|
||||
// 时间戳(毫秒)
|
||||
public long timestamp;
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
@Data
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public abstract class MovingObject {
|
||||
|
||||
// 当前坐标
|
||||
public GeoPosition currentPosition;
|
||||
//速度
|
||||
public Velocity velocity;
|
||||
// 航向(度)
|
||||
public double heading;
|
||||
// 时间戳(毫秒)
|
||||
public long timestamp;
|
||||
// 历史状态队列
|
||||
public Deque<MovementState> stateHistory = new ArrayDeque<>();
|
||||
// 最大历史记录数
|
||||
public int MAX_HISTORY = 10;
|
||||
// 最大速度(子类初始化)
|
||||
public double maxSpeed;
|
||||
// 类型枚举
|
||||
public MovingObjectType type;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
public enum MovingObjectType {
|
||||
// 航空器(飞机)
|
||||
AIRCRAFT,
|
||||
// 特勤车辆(不可控)
|
||||
SPECIAL_VEHICLE,
|
||||
// 无人车(可控)
|
||||
UNMANNED_VEHICLE
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
public class PositionRecord {
|
||||
private final GeoPosition position;
|
||||
private final long timestamp; // 时间戳(毫秒)
|
||||
|
||||
public PositionRecord(GeoPosition position, long timestamp) {
|
||||
this.position = position;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public GeoPosition getPosition() { return position; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SpecialVehicle extends MovingObject{
|
||||
/**
|
||||
* 车牌号
|
||||
*/
|
||||
@NotBlank(message = "车牌号不能为空")
|
||||
@JsonProperty("vehicleNo")
|
||||
private String vehicleNo;
|
||||
|
||||
|
||||
public SpecialVehicle(
|
||||
@JsonProperty("latitude") double latitude,
|
||||
@JsonProperty("longitude") double longitude,
|
||||
@JsonProperty("time") long timestamp,
|
||||
@JsonProperty("speed") double speed,
|
||||
@JsonProperty("direction") double heading
|
||||
) {
|
||||
this.currentPosition = new GeoPosition(latitude, longitude, 0);
|
||||
double radians = Math.toRadians(heading);
|
||||
double vx = speed *Math.sin(radians);
|
||||
double vy = speed *Math.cos(radians);
|
||||
// 对微小值归零(阈值可根据需求调整)
|
||||
vx = Math.abs(vx) < 1e-10 ? 0.0 : vx;
|
||||
vy = Math.abs(vy) < 1e-10 ? 0.0 : vy;
|
||||
this.velocity = new Velocity(vx,vy,0);
|
||||
this.heading = heading;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* 车辆实体类
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UnmannedVehicle extends MovingObject{
|
||||
/**
|
||||
* 消息唯一ID,消息的唯一标识符
|
||||
*/
|
||||
private String transId;
|
||||
/**
|
||||
* 车牌号
|
||||
*/
|
||||
@NotBlank(message = "车牌号不能为空")
|
||||
private String vehicleId;
|
||||
|
||||
public UnmannedVehicle(
|
||||
@JsonProperty("longitude") double longitude,
|
||||
@JsonProperty("latitude") double latitude,
|
||||
@JsonProperty("direction") double heading,
|
||||
@JsonProperty("speed") double speed
|
||||
) {
|
||||
this.currentPosition = new GeoPosition(longitude, latitude, 0);
|
||||
double vx = speed *Math.sin(heading);
|
||||
double vy = speed *Math.cos(heading);
|
||||
// 对微小值归零(阈值可根据需求调整)
|
||||
vx = Math.abs(vx) < 1e-10 ? 0.0 : vx;
|
||||
vy = Math.abs(vy) < 1e-10 ? 0.0 : vy;
|
||||
this.velocity = new Velocity(vx,vy,0);
|
||||
this.heading = heading;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.dongni.collisionavoidance.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Velocity {
|
||||
// 东向速度(米/秒)
|
||||
private final double x;
|
||||
// 北向速度(米/秒)
|
||||
private final double y;
|
||||
// 垂直速度(米/秒)
|
||||
private final double z;
|
||||
|
||||
|
||||
// 计算速度标量
|
||||
public double getSpeed() {
|
||||
return Math.sqrt(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.dongni.collisionavoidance.common.model.base;
|
||||
|
||||
public class Constant {
|
||||
|
||||
public static final String VEHICLE_LOCATION_KEY_PREFIX = "vehicle:location:";
|
||||
// 60秒后过期
|
||||
public static final long LOCATION_EXPIRE_TIME = 60;
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package com.dongni.collisionavoidance.common.model.base;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 通用响应对象
|
||||
* @param <T> 响应数据类型
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Response<T> {
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
public static <T> Response<T> success(T data) {
|
||||
return Response.<T>builder()
|
||||
.status(200)
|
||||
.message("操作成功")
|
||||
.data(data)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功响应(带自定义消息)
|
||||
*/
|
||||
public static <T> Response<T> success(String message, T data) {
|
||||
return Response.<T>builder()
|
||||
.status(200)
|
||||
.message(message)
|
||||
.data(data)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误响应
|
||||
*/
|
||||
public static <T> Response<T> error(Integer status, String message) {
|
||||
return Response.<T>builder()
|
||||
.status(status)
|
||||
.message(message)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误响应(默认500错误)
|
||||
*/
|
||||
public static <T> Response<T> error(String message) {
|
||||
return error(500, message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.dongni.collisionavoidance.common.model.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
// 对应原始数据结构
|
||||
@Data
|
||||
public class AircraftDTO {
|
||||
private String flightNo;
|
||||
private Long trackNumber;
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
private double altitude;
|
||||
private long time;
|
||||
|
||||
// 必须包含默认构造器
|
||||
public AircraftDTO() {}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.dongni.collisionavoidance.common.model.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SpecialVehicleDTO {
|
||||
|
||||
@NotBlank(message = "车牌号不能为空")
|
||||
private String vehicleNo;
|
||||
|
||||
@NotNull(message = "经度不能为空")
|
||||
private Double longitude;
|
||||
|
||||
@NotNull(message = "纬度不能为空")
|
||||
private Double latitude;
|
||||
|
||||
@NotNull(message = "时间戳不能为空")
|
||||
private Long time;
|
||||
|
||||
private Double direction;
|
||||
|
||||
private Double speed;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.dongni.collisionavoidance.config;
|
||||
|
||||
import com.dongni.collisionavoidance.dataCollector.model.VehicleLocationInfo;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, VehicleLocationInfo> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, VehicleLocationInfo> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
||||
Jackson2JsonRedisSerializer<VehicleLocationInfo> serializer =
|
||||
new Jackson2JsonRedisSerializer<>(VehicleLocationInfo.class);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
serializer.setObjectMapper(mapper);
|
||||
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
// @Bean
|
||||
// public RestTemplate restTemplate() {
|
||||
// return new RestTemplate();
|
||||
// }
|
||||
@Bean
|
||||
public RestTemplate restTemplate(ObjectMapper objectMapper) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.getMessageConverters().forEach(converter -> {
|
||||
if (converter instanceof MappingJackson2HttpMessageConverter) {
|
||||
((MappingJackson2HttpMessageConverter) converter).setObjectMapper(objectMapper);
|
||||
}
|
||||
});
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.dao;
|
||||
|
||||
|
||||
import com.dongni.collisionavoidance.common.model.Aircraft;
|
||||
import com.dongni.collisionavoidance.common.model.SpecialVehicle;
|
||||
import com.dongni.collisionavoidance.common.model.UnmannedVehicle;
|
||||
import com.dongni.collisionavoidance.common.model.base.Response;
|
||||
import com.dongni.collisionavoidance.common.model.dto.AircraftDTO;
|
||||
import com.dongni.collisionavoidance.common.model.dto.SpecialVehicleDTO;
|
||||
import com.dongni.collisionavoidance.dataCollector.document.VehicleLocationDocument;
|
||||
import com.dongni.collisionavoidance.dataCollector.model.VehicleLocationInfo;
|
||||
import com.dongni.collisionavoidance.dataCollector.service.AuthService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
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;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DataCollectorDao {
|
||||
|
||||
// 无人车厂商数据源相关配置
|
||||
@Value("${data.collector.vehicle-api.base-url}")
|
||||
private String vehicleBaseUrl;
|
||||
|
||||
@Value("${data.collector.vehicle-api.endpoints.vehicle-location}")
|
||||
private String vehicleLocationEndpoint;
|
||||
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final AuthService authService;
|
||||
|
||||
public DataCollectorDao(RestTemplate restTemplate, AuthService authService) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.authService = authService;
|
||||
}
|
||||
|
||||
|
||||
public List<Aircraft> collectAircraftData(String endpoint, String baseUrl) {
|
||||
try {
|
||||
String url = UriComponentsBuilder
|
||||
.fromHttpUrl(baseUrl)
|
||||
.path(endpoint)
|
||||
.toUriString();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", authService.getToken());
|
||||
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<Response<List<Aircraft>>> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
requestEntity,
|
||||
new ParameterizedTypeReference<Response<List<Aircraft>>>() {}
|
||||
);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
List<Aircraft> dataList = response.getBody().getData();
|
||||
log.info("成功获取航空器数据,数量: {}", dataList.size());
|
||||
return dataList;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("采集航空器数据失败: {}", endpoint, e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<SpecialVehicle> collectVehicleData(String endpoint, String baseUrl) {
|
||||
try {
|
||||
String url = UriComponentsBuilder
|
||||
.fromHttpUrl(baseUrl)
|
||||
.path(endpoint)
|
||||
.toUriString();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", authService.getToken());
|
||||
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<Response<List<SpecialVehicle>>> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
requestEntity,
|
||||
new ParameterizedTypeReference<Response<List<SpecialVehicle>>>() {}
|
||||
);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
List<SpecialVehicle> dataList = response.getBody().getData();
|
||||
log.info("成功获取特种车辆数据,数量: {}", dataList.size());
|
||||
return dataList;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("采集特种车辆数据失败: {}", endpoint, e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取车辆定位信息
|
||||
* @return 车辆定位信息列表
|
||||
*/
|
||||
public List<UnmannedVehicle> getVehicleLocationInfo() {
|
||||
System.out.println("接口被调用");
|
||||
try {
|
||||
String url = UriComponentsBuilder
|
||||
.fromHttpUrl(vehicleBaseUrl)
|
||||
.path(vehicleLocationEndpoint)
|
||||
.toUriString();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<List<UnmannedVehicle>> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
requestEntity,
|
||||
new ParameterizedTypeReference<List<UnmannedVehicle>>() {}
|
||||
);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
log.info("成功获取车辆定位信息,数量: {}", response.getBody().size());
|
||||
return response.getBody();
|
||||
} else {
|
||||
log.error("获取车辆定位信息失败,状态码: {}", response.getStatusCode());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取车辆定位信息时发生异常", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.document;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Document(collection = "vehicle_locations")
|
||||
@Data
|
||||
public class VehicleLocationDocument {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private String vehicleId;
|
||||
private double longitude;
|
||||
private double latitude;
|
||||
private double direction;
|
||||
private double speed;
|
||||
private long timestamp;
|
||||
|
||||
@Indexed
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CommandResponse {
|
||||
private int code; // 状态码
|
||||
private String msg; // 消息
|
||||
private String transId; // 消息唯一id
|
||||
private long timestamp; // 时间戳
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.model;
|
||||
|
||||
import com.dongni.collisionavoidance.dataCollector.model.enums.CommandType;
|
||||
import com.dongni.collisionavoidance.dataCollector.model.enums.CommandReason;
|
||||
import com.dongni.collisionavoidance.dataCollector.model.enums.SignalState;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class VehicleCommand {
|
||||
private String transId; // 消息唯一id
|
||||
private long timestamp; // 时间戳
|
||||
private String vehicleId; // 车辆ID
|
||||
private CommandType commandType; // 指令类型
|
||||
private CommandReason commandReason; // 指令原因
|
||||
private SignalState signalState; // 信号灯状态
|
||||
private String intersectionId; // 路口ID
|
||||
private double latitude; // 目标位置纬度
|
||||
private double longitude; // 目标位置经度
|
||||
private double relativeSpeed; // 相对速度
|
||||
private double relativeMotionX; // 相对运动X分量
|
||||
private double relativeMotionY; // 相对运动Y分量
|
||||
private double minDistance; // 最小距离
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class VehicleLocationInfo {
|
||||
private String transId; // 消息唯一id
|
||||
private long timestamp; // 时间戳
|
||||
private String vehicleId; // 车辆ID
|
||||
private double longitude; // 经度
|
||||
private double latitude; // 纬度
|
||||
private double direction; // 车头航向角
|
||||
private double speed; // 车速
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.model;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class VehicleStateInfo {
|
||||
private String transId;
|
||||
private long timestamp;
|
||||
private String vehicleId;
|
||||
private boolean loginStatus;
|
||||
private List<String> faultInfo;
|
||||
private boolean activeSafety;
|
||||
private boolean rc;
|
||||
private int command;
|
||||
private List<String> airportInfo;
|
||||
private int vehicleMode;
|
||||
private int gearState;
|
||||
private boolean chassisReady;
|
||||
private boolean collisionStatus;
|
||||
private int clearance;
|
||||
private int turnSignalStatus;
|
||||
private List<Byte> pointCloud;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.model.enums;
|
||||
|
||||
public enum CommandReason {
|
||||
TRAFFIC_LIGHT, // 红绿灯控制
|
||||
AIRCRAFT_CROSSING, // 航空器交叉
|
||||
SPECIAL_VEHICLE, // 特勤车辆
|
||||
AIRCRAFT_PUSH, // 航空器推出
|
||||
RESUME_TRAFFIC, // 恢复通行
|
||||
PARKING_SIDE // 安全停靠
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.model.enums;
|
||||
|
||||
public enum CommandType {
|
||||
ALERT, // 告警指令
|
||||
SIGNAL, // 信号灯指令
|
||||
WARNING, // 预警指令
|
||||
RESUME, // 恢复指令
|
||||
PARKING // 安全停靠
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.model.enums;
|
||||
|
||||
public enum SignalState {
|
||||
RED, // 红灯
|
||||
YELLOW, // 黄灯
|
||||
GREEN // 绿灯
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.repository;
|
||||
|
||||
import com.dongni.collisionavoidance.dataCollector.document.VehicleLocationDocument;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface VehicleLocationRepository extends MongoRepository<VehicleLocationDocument, String> {
|
||||
List<VehicleLocationDocument> findByVehicleIdAndTimestampBetween(
|
||||
String vehicleId, long startTime, long endTime);
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.service;
|
||||
|
||||
|
||||
import com.dongni.collisionavoidance.common.model.base.Response;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
||||
@Value("${data.collector.airport-api.auth.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${data.collector.airport-api.auth.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${data.collector.airport-api.base-url}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${data.collector.airport-api.endpoints.login}")
|
||||
private String loginEndpoint;
|
||||
|
||||
@Value("${data.collector.airport-api.endpoints.refresh}")
|
||||
private String refreshEndpoint;
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private String token;
|
||||
private long tokenExpiryTime;
|
||||
|
||||
public AuthService(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
//登录获取Token
|
||||
public String loginAndGetToken() {
|
||||
String loginUrl = UriComponentsBuilder
|
||||
.fromHttpUrl(baseUrl)
|
||||
.path(loginEndpoint)
|
||||
.queryParam("username", username)
|
||||
.queryParam("password", password)
|
||||
.toUriString();
|
||||
|
||||
try {
|
||||
ResponseEntity<Response<String>> response = restTemplate.exchange(
|
||||
loginUrl,
|
||||
HttpMethod.POST,
|
||||
null,
|
||||
new ParameterizedTypeReference<>() {
|
||||
}
|
||||
);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
this.token = response.getBody().getData();
|
||||
this.tokenExpiryTime = System.currentTimeMillis() + 3600 * 1000;
|
||||
log.info("Successfully obtained new token");
|
||||
return this.token;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to login: ", e);
|
||||
}
|
||||
throw new RuntimeException("Failed to obtain token");
|
||||
}
|
||||
|
||||
//Token续时
|
||||
public String refreshToken() {
|
||||
String refreshUrl = UriComponentsBuilder
|
||||
.fromHttpUrl(baseUrl)
|
||||
.path(refreshEndpoint)
|
||||
.toUriString();
|
||||
|
||||
try {
|
||||
// 创建带有当前token的请求头
|
||||
HttpEntity<?> requestEntity = new HttpEntity<>(createAuthHeader());
|
||||
|
||||
ResponseEntity<Response<String>> response = restTemplate.exchange(
|
||||
refreshUrl,
|
||||
HttpMethod.GET,
|
||||
requestEntity,
|
||||
new ParameterizedTypeReference<Response<String>>() {}
|
||||
);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
this.token = response.getBody().getData();
|
||||
this.tokenExpiryTime = System.currentTimeMillis() + 3600 * 1000;
|
||||
log.info("Successfully refreshed token");
|
||||
return this.token;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to refresh token: ", e);
|
||||
}
|
||||
// 如果续期失败,尝试重新登录
|
||||
return loginAndGetToken();
|
||||
}
|
||||
|
||||
//创造带有Token的请求头
|
||||
private HttpHeaders createAuthHeader() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
if (token != null) {
|
||||
headers.set("Authorization", token);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
//获取Token
|
||||
public String getToken() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (token == null) {
|
||||
return loginAndGetToken();
|
||||
}
|
||||
// 如果token已过期,重新登录
|
||||
if (currentTime >= tokenExpiryTime) {
|
||||
return loginAndGetToken();
|
||||
}
|
||||
// 如果token即将过期(比如还有10分钟过期),尝试续期
|
||||
if (currentTime >= tokenExpiryTime - 600_000) {
|
||||
return refreshToken();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.service;
|
||||
|
||||
import com.dongni.collisionavoidance.dataCollector.document.VehicleLocationDocument;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DataCleanupService {
|
||||
// private final MongoTemplate mongoTemplate;
|
||||
//
|
||||
// @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
|
||||
// public void cleanupOldData() {
|
||||
// LocalDateTime threshold = LocalDateTime.now().minusDays(30);
|
||||
// Query query = Query.query(Criteria.where("createTime").lt(threshold));
|
||||
// DeleteResult result = mongoTemplate.remove(query, VehicleLocationDocument.class);
|
||||
// log.info("Cleaned up {} old location records", result.getDeletedCount());
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,208 @@
|
||||
package com.dongni.collisionavoidance.dataCollector.service;
|
||||
|
||||
import com.dongni.collisionavoidance.common.model.*;
|
||||
import com.dongni.collisionavoidance.common.model.dto.AircraftDTO;
|
||||
import com.dongni.collisionavoidance.common.model.dto.SpecialVehicleDTO;
|
||||
import com.dongni.collisionavoidance.dataCollector.dao.DataCollectorDao;
|
||||
import com.dongni.collisionavoidance.dataCollector.model.VehicleLocationInfo;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DataCollectorService {
|
||||
|
||||
// 机场数据源相关配置
|
||||
@Value("${data.collector.airport-api.endpoints.vehicle}")
|
||||
private String airportVehicleEndpoint;
|
||||
|
||||
@Value("${data.collector.airport-api.endpoints.aircraft}")
|
||||
private String airportAircraftEndpoint;
|
||||
|
||||
@Value("${data.collector.airport-api.base-url}")
|
||||
private String airportBaseUrl;
|
||||
|
||||
// 线程安全队列(用于暂存原始数据)
|
||||
@Getter
|
||||
ConcurrentHashMap<String, List<Object>> dataMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 使用ConcurrentHashMap存储所有移动物体的最新状态
|
||||
// key为物体的唯一标识(如航班号),value为对应的移动物体
|
||||
private final ConcurrentHashMap<String, Aircraft> aircraftMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, SpecialVehicle> vehicleMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, UnmannedVehicle> unmannedVehicleMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final DataCollectorDao dataCollectorDao;
|
||||
|
||||
|
||||
public DataCollectorService(DataCollectorDao dataCollectorDao) {
|
||||
|
||||
this.dataCollectorDao = dataCollectorDao;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRateString = "${data.collector.interval}")
|
||||
@Async // 异步执行
|
||||
public void collectAircraftData() {
|
||||
List<Aircraft> newAircrafts = dataCollectorDao.collectAircraftData(airportAircraftEndpoint, airportBaseUrl);
|
||||
|
||||
for (Aircraft newAircraft : newAircrafts) {
|
||||
String flightNo = newAircraft.getFlightNo();
|
||||
// 获取已存在的航空器(如果存在)
|
||||
Aircraft existingAircraft = aircraftMap.get(flightNo);
|
||||
|
||||
if (existingAircraft != null) {
|
||||
// 更新现有航空器的状态
|
||||
MovementState currentState = new MovementState(
|
||||
newAircraft.getCurrentPosition(),
|
||||
newAircraft.getVelocity(),
|
||||
newAircraft.getHeading(),
|
||||
newAircraft.getTimestamp()
|
||||
);
|
||||
|
||||
// 使用已存在航空器的历史队列
|
||||
existingAircraft.setCurrentPosition(newAircraft.getCurrentPosition());
|
||||
existingAircraft.setVelocity(newAircraft.getVelocity());
|
||||
existingAircraft.setHeading(newAircraft.getHeading());
|
||||
existingAircraft.setTimestamp(newAircraft.getTimestamp());
|
||||
existingAircraft.getStateHistory().addFirst(currentState);
|
||||
|
||||
// 控制历史记录长度
|
||||
if (existingAircraft.getStateHistory().size() > existingAircraft.MAX_HISTORY) {
|
||||
existingAircraft.getStateHistory().removeLast();
|
||||
}
|
||||
} else {
|
||||
// 新的航空器,初始化历史记录
|
||||
MovementState initialState = new MovementState(
|
||||
newAircraft.getCurrentPosition(),
|
||||
newAircraft.getVelocity(),
|
||||
newAircraft.getHeading(),
|
||||
newAircraft.getTimestamp()
|
||||
);
|
||||
newAircraft.getStateHistory().addFirst(initialState);
|
||||
aircraftMap.put(flightNo, newAircraft);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新数据Map(用于其他服务访问)
|
||||
storeData(MovingObjectType.AIRCRAFT.toString(), new ArrayList<>(aircraftMap.values()));
|
||||
}
|
||||
|
||||
@Scheduled(fixedRateString = "${data.collector.interval}")
|
||||
@Async // 异步执行
|
||||
public void collectVehicleData() {
|
||||
List<SpecialVehicle> vehicles = dataCollectorDao.collectVehicleData(airportVehicleEndpoint, airportBaseUrl);
|
||||
for (SpecialVehicle newVehicle : vehicles) {
|
||||
String vehicleNo = newVehicle.getVehicleNo();
|
||||
// 获取已存在的航空器(如果存在)
|
||||
SpecialVehicle specialVehicle = vehicleMap.get(vehicleNo);
|
||||
if (specialVehicle != null) {
|
||||
// 更新现有航空器的状态
|
||||
MovementState currentState = new MovementState(
|
||||
newVehicle.getCurrentPosition(),
|
||||
newVehicle.getVelocity(),
|
||||
newVehicle.getHeading(),
|
||||
newVehicle.getTimestamp()
|
||||
);
|
||||
|
||||
// 使用已存在航空器的历史队列
|
||||
specialVehicle.setCurrentPosition(newVehicle.getCurrentPosition());
|
||||
specialVehicle.setVelocity(newVehicle.getVelocity());
|
||||
specialVehicle.setHeading(newVehicle.getHeading());
|
||||
specialVehicle.setTimestamp(newVehicle.getTimestamp());
|
||||
specialVehicle.getStateHistory().addFirst(currentState);
|
||||
|
||||
// 控制历史记录长度
|
||||
if (specialVehicle.getStateHistory().size() > specialVehicle.MAX_HISTORY) {
|
||||
specialVehicle.getStateHistory().removeLast();
|
||||
}
|
||||
} else {
|
||||
// 新的航空器,初始化历史记录
|
||||
MovementState initialState = new MovementState(
|
||||
newVehicle.getCurrentPosition(),
|
||||
newVehicle.getVelocity(),
|
||||
newVehicle.getHeading(),
|
||||
newVehicle.getTimestamp()
|
||||
);
|
||||
newVehicle.getStateHistory().addFirst(initialState);
|
||||
vehicleMap.put(vehicleNo, newVehicle);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新数据Map(用于其他服务访问)
|
||||
storeData(MovingObjectType.SPECIAL_VEHICLE.toString(), new ArrayList<>(vehicleMap.values()));
|
||||
}
|
||||
|
||||
|
||||
@Scheduled(fixedRateString = "${data.collector.interval}")
|
||||
@Async // 异步执行
|
||||
public void collectVehicleLocationData() {
|
||||
List<UnmannedVehicle> unmannedVehicles = dataCollectorDao.getVehicleLocationInfo();
|
||||
for (UnmannedVehicle newVehicle : unmannedVehicles) {
|
||||
String vehicleNo = newVehicle.getVehicleId();
|
||||
// 获取已存在的航空器(如果存在)
|
||||
UnmannedVehicle unmannedVehicle = unmannedVehicleMap.get(vehicleNo);
|
||||
if (unmannedVehicle != null) {
|
||||
// 更新现有航空器的状态
|
||||
MovementState currentState = new MovementState(
|
||||
newVehicle.getCurrentPosition(),
|
||||
newVehicle.getVelocity(),
|
||||
newVehicle.getHeading(),
|
||||
newVehicle.getTimestamp()
|
||||
);
|
||||
|
||||
// 使用已存在航空器的历史队列
|
||||
unmannedVehicle.setCurrentPosition(newVehicle.getCurrentPosition());
|
||||
unmannedVehicle.setVelocity(newVehicle.getVelocity());
|
||||
unmannedVehicle.setHeading(newVehicle.getHeading());
|
||||
unmannedVehicle.setTimestamp(newVehicle.getTimestamp());
|
||||
unmannedVehicle.getStateHistory().addFirst(currentState);
|
||||
|
||||
// 控制历史记录长度
|
||||
if (unmannedVehicle.getStateHistory().size() > unmannedVehicle.MAX_HISTORY) {
|
||||
unmannedVehicle.getStateHistory().removeLast();
|
||||
}
|
||||
} else {
|
||||
// 新的航空器,初始化历史记录
|
||||
MovementState initialState = new MovementState(
|
||||
newVehicle.getCurrentPosition(),
|
||||
newVehicle.getVelocity(),
|
||||
newVehicle.getHeading(),
|
||||
newVehicle.getTimestamp()
|
||||
);
|
||||
newVehicle.getStateHistory().addFirst(initialState);
|
||||
unmannedVehicleMap.put(vehicleNo, newVehicle);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新数据Map(用于其他服务访问)
|
||||
storeData(MovingObjectType.UNMANNED_VEHICLE.toString(), new ArrayList<>(unmannedVehicleMap.values()));
|
||||
}
|
||||
|
||||
|
||||
private <T> void storeData(String type, List<T> data) {
|
||||
|
||||
dataMap.put(type, new CopyOnWriteArrayList<>(data));
|
||||
}
|
||||
|
||||
private void validateAndUpdateTimestamp(MovementState state, LinkedList<MovementState> history) {
|
||||
if (!history.isEmpty()) {
|
||||
MovementState lastState = history.getFirst();
|
||||
if (state.getTimestamp() <= lastState.getTimestamp()) {
|
||||
log.warn("检测到时间戳乱序: current={}, last={}",
|
||||
state.getTimestamp(), lastState.getTimestamp());
|
||||
// 使用递增时间戳
|
||||
state.setTimestamp(lastState.getTimestamp() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
package com.dongni.collisionavoidance.dataProcessing.service;
|
||||
|
||||
import com.dongni.collisionavoidance.common.model.MovingObjectType;
|
||||
import com.dongni.collisionavoidance.dataCollector.service.DataCollectorService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DataProcessor {
|
||||
// 处理线程池
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(5);
|
||||
// private final RedisTemplate<String, Object> redisTemplate;
|
||||
// private final MongoTemplate mongoTemplate;
|
||||
private final DataCollectorService dataCollectorService;
|
||||
|
||||
public DataProcessor(
|
||||
// RedisTemplate<String, Object> redisTemplate,
|
||||
// MongoTemplate mongoTemplate,
|
||||
DataCollectorService dataCollectorService) {
|
||||
// this.redisTemplate = redisTemplate;
|
||||
// this.mongoTemplate = mongoTemplate;
|
||||
this.dataCollectorService = dataCollectorService;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
executor.execute(this::processLoop);
|
||||
}
|
||||
|
||||
|
||||
private void processLoop() {
|
||||
while (true) {
|
||||
try {
|
||||
// 获取共享的数据Map
|
||||
ConcurrentHashMap<String, List<Object>> currentDataMap = dataCollectorService.getDataMap();
|
||||
|
||||
// 遍历所有数据类型
|
||||
for (Map.Entry<String, List<Object>> entry : currentDataMap.entrySet()) {
|
||||
String dataType = entry.getKey();
|
||||
List<Object> dataList = entry.getValue();
|
||||
|
||||
if (dataList != null && !dataList.isEmpty()) {
|
||||
// 根据不同的数据类型进行处理
|
||||
switch (dataType) {
|
||||
case "AIRCRAFT"-> processAircraftData(dataList);
|
||||
case "SPECIAL_VEHICLE" -> processVehicleData(dataList);
|
||||
case "UNMANNED_VEHICLE"-> processLocationData(dataList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理完后休眠一段时间
|
||||
Thread.sleep(1000);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error in data processing loop", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 预留的处理方法
|
||||
private void processAircraftData(List<Object> dataList) {
|
||||
// TODO: 实现飞机数据处理逻辑
|
||||
System.out.println("processAircraftData" + dataList);
|
||||
}
|
||||
|
||||
private void processVehicleData(List<Object> dataList) {
|
||||
// TODO: 实现车辆数据处理逻辑
|
||||
System.out.println("processAircraftData" + dataList);
|
||||
}
|
||||
|
||||
private void processLocationData(List<Object> dataList) {
|
||||
// TODO: 实现位置数据处理逻辑
|
||||
System.out.println("processAircraftData" + dataList);
|
||||
}
|
||||
}
|
||||
|
||||
93
src/main/resources/application.yml
Normal file
93
src/main/resources/application.yml
Normal file
@ -0,0 +1,93 @@
|
||||
server:
|
||||
port: 8082
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: CollisionAvoidance
|
||||
# Kafka配置
|
||||
kafka:
|
||||
bootstrap-servers: 192.168.42.128:9092
|
||||
producer:
|
||||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
|
||||
acks: 1
|
||||
retries: 3
|
||||
# 消费者配置(如果需要订阅其他服务的消息)
|
||||
consumer:
|
||||
group-id: data-collector-group
|
||||
auto-offset-reset: latest
|
||||
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
|
||||
properties:
|
||||
spring.json.trusted.packages: "com.airport.common.model"
|
||||
|
||||
# Redis配置
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
database: 0
|
||||
timeout: 10000
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-wait: -1
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
key-serialization: org.springframework.data.redis.serialization.StringRedisSerializer
|
||||
value-serialization: org.springframework.data.redis.serialization.Jackson2JsonRedisSerializer
|
||||
|
||||
# 数据采集配置
|
||||
data:
|
||||
collector:
|
||||
interval: 1000
|
||||
topics:
|
||||
position: aircraft-positions
|
||||
vehicle: vehicle-positions
|
||||
# 机场数据源配置
|
||||
airport-api:
|
||||
base-url: http://localhost:8090
|
||||
endpoints:
|
||||
login: /login
|
||||
aircraft: /openApi/getCurrentFlightPositions
|
||||
vehicle: /openApi/getCurrentVehiclePositions
|
||||
refresh: /refresh
|
||||
auth:
|
||||
username: dianxin
|
||||
password: dianxin@123
|
||||
|
||||
# 无人车厂商数据源配置
|
||||
vehicle-api:
|
||||
base-url: http://127.0.0.1:31140
|
||||
endpoints:
|
||||
vehicle-location: /api/VehicleLocationInfo
|
||||
vehicle-state: /api/VehicleStateInfo
|
||||
vehicle-command: /api/VehicleCommandInfo
|
||||
# MongoDB配置
|
||||
mongodb:
|
||||
uri: mongodb://localhost:27017/vehicle_tracking
|
||||
retention:
|
||||
redis-expire-seconds: 60
|
||||
mongodb-days: 30
|
||||
# 数据保留策略配置
|
||||
# Actuator配置
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
logging:
|
||||
level:
|
||||
org:
|
||||
springframework:
|
||||
web:
|
||||
client:
|
||||
RestTemplate: DEBUG
|
||||
|
||||
http:
|
||||
converter:
|
||||
json: TRACE
|
||||
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
package com.dongni.collisionavoidance;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class CollisionAvoidanceApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
|
||||
服务器将在 http://127.0.0.1:31140 启动。
|
||||
|
||||
## 可用接口
|
||||
|
||||
1. 获取车辆定位信息
|
||||
- URL: GET http://127.0.0.1:31140/api/VehicleLocationInfo
|
||||
|
||||
2. 获取车辆状态信息
|
||||
- URL: POST http://127.0.0.1:31140/api/VehicleStateInfo
|
||||
|
||||
3. 发送指令接口
|
||||
- URL: POST http://127.0.0.1:31140/api/VehicleCommandInfo
|
||||
|
||||
## API文档
|
||||
|
||||
启动服务器后,访问 http://127.0.0.1:31140/docs 可以查看完整的API文档。
|
||||
|
||||
|
||||
让我为您分析这两个mock服务器的数据:
|
||||
|
||||
### 1. mock_server.py(主要mock服务器)
|
||||
|
||||
#### 飞机数据:
|
||||
- 1架飞机:`AC001`
|
||||
- 初始位置:从T7点出发
|
||||
- 运动路线:T7 -> T11,到达后返回起点
|
||||
- 速度:36 km/h
|
||||
|
||||
#### 车辆数据:
|
||||
总共3辆车:
|
||||
1. `QN001`(无人车1)
|
||||
- 路线:T1 -> T2 -> T4,到达后返回T1
|
||||
- 速度:默认18 km/h
|
||||
|
||||
2. `QN002`(无人车2)
|
||||
- 路线:T12 -> T8,到达后返回T12
|
||||
- 速度:默认18 km/h
|
||||
|
||||
3. `TQ001`(特勤车)
|
||||
- 路线:T4 -> T2 -> T3,到达后返回T4
|
||||
- 速度:默认18 km/h
|
||||
|
||||
#### 红绿灯数据:
|
||||
两个路口的红绿灯:
|
||||
1. `TL001`(西路口,T2点)
|
||||
2. `TL002`(东路口,T6点)
|
||||
|
||||
### 2. vehicle_mock_server.py(车辆mock服务器)
|
||||
|
||||
这是一个独立的车辆模拟服务器:
|
||||
- 随机生成5辆车(编号AT001-AT005)
|
||||
- 在两条预定义路线上随机行驶
|
||||
- 速度:5-8米/秒
|
||||
- 位置实时更新
|
||||
|
||||
### 数据传递方式:
|
||||
|
||||
1. mock_server.py 提供以下API:
|
||||
```python
|
||||
- /openApi/getCurrentFlightPositions # 获取飞机位置
|
||||
- /openApi/getCurrentVehiclePositions # 获取车辆位置
|
||||
- /openApi/getTrafficLightSignals # 获取红绿灯状态
|
||||
- /openApi/getVehicleStatus # 获取车辆状态
|
||||
- /login # 登录认证
|
||||
```
|
||||
|
||||
2. vehicle_mock_server.py 提供以下API:
|
||||
```python
|
||||
- /api/VehicleLocationInfo # 获取车辆位置
|
||||
- /api/VehicleStateInfo # 获取车辆状态
|
||||
- /api/VehicleCommandInfo # 接收车辆指令
|
||||
```
|
||||
|
||||
### 总结:
|
||||
这两个mock服务器模拟了不同场景:
|
||||
1. mock_server.py 模拟机场场景,包含1架飞机、3辆车和2个红绿灯路口的完整交通系统
|
||||
2. vehicle_mock_server.py 则是一个独立的车辆模拟系统,随机生成5辆车的运动数据
|
||||
|
||||
这两个服务器运行在不同端口:
|
||||
- mock_server.py: 8081
|
||||
- vehicle_mock_server.py: 31140
|
||||
|
||||
它们可以同时运行,为测试提供不同的数据源。
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,251 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional, Dict
|
||||
import uvicorn
|
||||
import random
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
import math
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
|
||||
# 设置日志
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# 枚举定义
|
||||
class CommandType(str, Enum):
|
||||
ALERT = "ALERT"
|
||||
SIGNAL = "SIGNAL"
|
||||
WARNING = "WARNING"
|
||||
RESUME = "RESUME"
|
||||
PARKING = "PARKING"
|
||||
|
||||
class CommandReason(str, Enum):
|
||||
TRAFFIC_LIGHT = "TRAFFIC_LIGHT"
|
||||
AIRCRAFT_CROSSING = "AIRCRAFT_CROSSING"
|
||||
SPECIAL_VEHICLE = "SPECIAL_VEHICLE"
|
||||
AIRCRAFT_PUSH = "AIRCRAFT_PUSH"
|
||||
RESUME_TRAFFIC = "RESUME_TRAFFIC"
|
||||
PARKING_SIDE = "PARKING_SIDE"
|
||||
|
||||
class SignalState(str, Enum):
|
||||
RED = "RED"
|
||||
GREEN = "GREEN"
|
||||
|
||||
# 数据模型
|
||||
class VehicleLocationInfo(BaseModel):
|
||||
transId: str
|
||||
timestamp: int
|
||||
vehicleId: str
|
||||
longitude: float
|
||||
latitude: float
|
||||
direction: float
|
||||
speed: float
|
||||
|
||||
class VehicleStateInfo(BaseModel):
|
||||
transId: str
|
||||
timestamp: int
|
||||
vehicleId: str
|
||||
loginStatus: bool
|
||||
faultInfo: List[str]
|
||||
activeSafety: bool
|
||||
rc: bool
|
||||
command: int
|
||||
airportInfo: List[str]
|
||||
vehicleMode: int
|
||||
gearState: int
|
||||
chassisReady: bool
|
||||
collisionStatus: bool
|
||||
clearance: int
|
||||
turnSignalStatus: int
|
||||
pointCloud: List[int]
|
||||
|
||||
class CommandRequest(BaseModel):
|
||||
transId: str
|
||||
timestamp: int
|
||||
vehicleId: str
|
||||
commandType: CommandType
|
||||
commandReason: CommandReason
|
||||
signalState: Optional[SignalState]
|
||||
intersectionId: Optional[str]
|
||||
latitude: float
|
||||
longitude: float
|
||||
relativeSpeed: Optional[float]
|
||||
relativeMotionX: Optional[float]
|
||||
relativeMotionY: Optional[float]
|
||||
minDistance: Optional[float]
|
||||
|
||||
class CommandResponse(BaseModel):
|
||||
code: int
|
||||
msg: str
|
||||
transId: str
|
||||
timestamp: int
|
||||
|
||||
# 预定义的路线点(以机场为例的一些路线)
|
||||
ROUTES = {
|
||||
"ROUTE1": [
|
||||
(121.805, 31.151), # 起点
|
||||
(121.807, 31.152), # 转弯点1
|
||||
(121.809, 31.153), # 转弯点2
|
||||
(121.811, 31.154), # 终点
|
||||
],
|
||||
"ROUTE2": [
|
||||
(121.806, 31.155),
|
||||
(121.808, 31.156),
|
||||
(121.810, 31.157),
|
||||
(121.812, 31.158),
|
||||
]
|
||||
}
|
||||
|
||||
# 存储车辆当前状态
|
||||
vehicle_states: Dict[str, dict] = {}
|
||||
|
||||
class TimeStampGenerator:
|
||||
def __init__(self):
|
||||
self.last_timestamp = int(time.time() * 1000)
|
||||
self.sequence = 0
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def next_timestamp(self):
|
||||
with self.lock:
|
||||
current = int(time.time() * 1000)
|
||||
if current == self.last_timestamp:
|
||||
self.sequence += 1
|
||||
else:
|
||||
self.sequence = 0
|
||||
self.last_timestamp = current
|
||||
return self.last_timestamp + self.sequence
|
||||
|
||||
timestamp_generator = TimeStampGenerator()
|
||||
|
||||
def calculate_direction(p1, p2):
|
||||
"""计算两点之间的方向角(弧度)"""
|
||||
dx = p2[0] - p1[0]
|
||||
dy = p2[1] - p1[1]
|
||||
return math.atan2(dy, dx)
|
||||
|
||||
def calculate_next_position(current_pos, target_pos, speed):
|
||||
"""计算下一个位置点"""
|
||||
# 将速度从米/秒转换为经纬度增量
|
||||
speed_factor = speed * 0.00001 # 简化的转换因子
|
||||
|
||||
dx = target_pos[0] - current_pos[0]
|
||||
dy = target_pos[1] - current_pos[1]
|
||||
distance = math.sqrt(dx*dx + dy*dy)
|
||||
|
||||
if distance < speed_factor:
|
||||
return target_pos
|
||||
|
||||
ratio = speed_factor / distance
|
||||
new_x = current_pos[0] + dx * ratio
|
||||
new_y = current_pos[1] + dy * ratio
|
||||
|
||||
return (new_x, new_y)
|
||||
|
||||
def get_or_create_vehicle_state(vehicle_id: str) -> dict:
|
||||
"""获取或创建车辆状态"""
|
||||
if vehicle_id not in vehicle_states:
|
||||
route = random.choice(list(ROUTES.values()))
|
||||
vehicle_states[vehicle_id] = {
|
||||
'current_pos': route[0],
|
||||
'route': route,
|
||||
'route_index': 0,
|
||||
'speed': random.uniform(5.0, 8.0) # 5-8米/秒
|
||||
}
|
||||
return vehicle_states[vehicle_id]
|
||||
|
||||
def update_vehicle_position(vehicle_id: str, state: dict) -> tuple:
|
||||
"""更新车辆位置"""
|
||||
current_pos = state['current_pos']
|
||||
route = state['route']
|
||||
route_index = state['route_index']
|
||||
|
||||
if route_index >= len(route) - 1:
|
||||
route_index = 0
|
||||
|
||||
target_pos = route[route_index + 1]
|
||||
next_pos = calculate_next_position(current_pos, target_pos, state['speed'])
|
||||
|
||||
# 如果到达目标点,移动到下一个路线点
|
||||
if next_pos == target_pos:
|
||||
route_index += 1
|
||||
if route_index >= len(route) - 1:
|
||||
route_index = 0
|
||||
|
||||
state['current_pos'] = next_pos
|
||||
state['route_index'] = route_index
|
||||
|
||||
direction = calculate_direction(current_pos, target_pos)
|
||||
return next_pos, direction
|
||||
|
||||
def generate_vehicle_location():
|
||||
vehicle_id = f"AT{random.randint(1, 5):03d}" # 限制车辆数量为5辆
|
||||
state = get_or_create_vehicle_state(vehicle_id)
|
||||
|
||||
position, direction = update_vehicle_position(vehicle_id, state)
|
||||
|
||||
vehicle_info = VehicleLocationInfo(
|
||||
transId=str(uuid.uuid4()),
|
||||
timestamp=timestamp_generator.next_timestamp(),
|
||||
vehicleId=vehicle_id,
|
||||
longitude=position[0],
|
||||
latitude=position[1],
|
||||
direction=direction,
|
||||
speed=state['speed']
|
||||
)
|
||||
|
||||
logger.info(f"Vehicle {vehicle_id} position updated - Longitude: {position[0]:.6f}, "
|
||||
f"Latitude: {position[1]:.6f}, Direction: {direction:.6f} rad, "
|
||||
f"Speed: {state['speed']:.2f} m/s")
|
||||
|
||||
return vehicle_info
|
||||
|
||||
def generate_vehicle_state():
|
||||
return VehicleStateInfo(
|
||||
transId=str(uuid.uuid4()),
|
||||
timestamp=int(datetime.now().timestamp() * 1000),
|
||||
vehicleId=f"AT{random.randint(1, 999):03d}",
|
||||
loginStatus=random.choice([True, False]),
|
||||
faultInfo=[],
|
||||
activeSafety=random.choice([True, False]),
|
||||
rc=False,
|
||||
command=0,
|
||||
airportInfo=[],
|
||||
vehicleMode=random.randint(1, 5),
|
||||
gearState=random.randint(1, 5),
|
||||
chassisReady=True,
|
||||
collisionStatus=False,
|
||||
clearance=random.randint(0, 1),
|
||||
turnSignalStatus=random.randint(0, 3),
|
||||
pointCloud=[]
|
||||
)
|
||||
|
||||
# API端点
|
||||
@app.get("/api/VehicleLocationInfo", response_model=List[VehicleLocationInfo])
|
||||
async def get_vehicle_location():
|
||||
vehicles = [generate_vehicle_location() for _ in range(5)] # 固定生成5辆车的数据
|
||||
logger.info(f"Returning location data for {len(vehicles)} vehicles")
|
||||
return vehicles
|
||||
|
||||
@app.post("/api/VehicleStateInfo", response_model=List[VehicleStateInfo])
|
||||
async def get_vehicle_state():
|
||||
return [generate_vehicle_state() for _ in range(random.randint(1, 5))]
|
||||
|
||||
@app.post("/api/VehicleCommandInfo", response_model=CommandResponse)
|
||||
async def vehicle_command(command: CommandRequest):
|
||||
return CommandResponse(
|
||||
code=200,
|
||||
msg="success",
|
||||
transId=command.transId,
|
||||
timestamp=int(datetime.now().timestamp() * 1000)
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("Starting mock vehicle server on port 31140...")
|
||||
uvicorn.run(app, host="127.0.0.1", port=31140)
|
||||
Loading…
Reference in New Issue
Block a user