4.28第一次提交

This commit is contained in:
2026-04-28 16:21:46 +08:00
commit 3bc1e3194f
29 changed files with 1473 additions and 0 deletions

18
.claude/settings.json Normal file
View File

@@ -0,0 +1,18 @@
{
"permissions": {
"allow": [
"Read(//home/zuoww/.m2/**)",
"Read(//home/zuoww/.m2/repository/**)",
"Read(//home/zuoww/.m2/repository/com/tencentcloudapi/**)",
"Bash(mvn dependency:resolve -q)",
"Bash(./mvnw dependency:resolve -q)",
"Bash(chmod +x \"/home/zuoww/vscodeworkspace/vindata 1/vindata/mvnw\")",
"Bash(java -version)",
"Read(//usr/lib/**)",
"Read(//usr/local/**)",
"Read(//etc/alternatives/**)",
"Read(//home/zuoww/**)",
"WebFetch(domain:cloud.tencent.com)"
]
}
}

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
.gitignore vendored Normal file
View File

@@ -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/

3
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip

26
Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
# 构建阶段Maven + JDK17 打包
FROM maven:3.8.8-openjdk-17-slim AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# 运行阶段:轻量 JRE17 镜像
FROM eclipse-temurin:17-jre-alpine
# 设置上海时区和你Python代码时间一致
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
WORKDIR /app
# 复制打好的jar
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
#ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["java", \
"--add-opens", "java.base/java.nio=ALL-UNNAMED", \
"--add-opens", "java.base/sun.misc=ALL-UNNAMED", \
"-Dio.netty.tryUnsafe=false", \
"-jar", "app.jar"]

295
mvnw vendored Normal file
View File

@@ -0,0 +1,295 @@
#!/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.4
#
# 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:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# 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 <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.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${scriptName#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
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

189
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,189 @@
<# : 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.4
@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 -eq $False) { "/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_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::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
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -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"

134
pom.xml Normal file
View File

@@ -0,0 +1,134 @@
<?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.15</version>
<relativePath/>
</parent>
<groupId>com.mystarvindata</groupId>
<artifactId>vindata</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>vindata</name>
<description>微软云 + WeData 对接项目</description>
<!-- 🔥 添加Databricks官方仓库解决驱动下载不到 -->
<repositories>
<!-- 🔥 Maven中央仓库必须加下载腾讯云/所有公共依赖) -->
<repository>
<id>central</id>
<name>Maven Central</name>
<url>https://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!-- Databricks仓库保留你的驱动需要用 -->
<repository>
<id>databricks</id>
<url>https://maven.databricks.com/repo/public</url>
</repository>
</repositories>
<properties>
<java.version>17</java.version>
<azure.version>1.14.0</azure.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>${azure.version}</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
<version>${azure.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.31</version>
</dependency>
<dependency>
<groupId>com.databricks</groupId>
<artifactId>databricks-jdbc</artifactId>
<version>2.6.25</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-wedata</artifactId>
<version>3.1.1452</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId> <!-- 全产品SDK -->
<version>3.1.1439</version>
</dependency>
<!-- 腾讯云 DLC SDK -->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-dlc</artifactId>
<!-- 请检查并使用最新版本,目前常见版本为 3.1.x 或更高 -->
<version>3.1.1439</version>
</dependency>
<!-- &lt;!&ndash; 核心库 (通常也需要) &ndash;&gt;-->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-common</artifactId>
<version>3.1.1452</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,13 @@
package com.mystarvindata.vindata;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class VindataApplication {
public static void main(String[] args) {
SpringApplication.run(VindataApplication.class, args);
}
}

View File

@@ -0,0 +1,7 @@
package com.mystarvindata.vindata.common;
public class Constants {
// 替换为你真实的 GEN5/GEN6 表名
public static final String GEN5_TABLE = "hive_metastore.taf_level_two_plus.the_140th_anniversary";
public static final String GEN6_TABLE = "dws.the_140th_anniversary";
}

View File

@@ -0,0 +1,40 @@
package com.mystarvindata.vindata.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 云服务配置读取对应Python的AZURE_SPARK_CONFIG + WEDATA_CONFIG
*/
@Data
@Component
public class CloudConfig {
/**
* 微软云 Databricks 配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "azure.databricks")
public static class AzureDatabricksConfig {
private String host;
private String token;
private String clusterId;
private String httpPath;
}
/**
* WeData 配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "wedata")
public static class WeDataConfig {
private String accessKey;
private String secretKey;
private String projectId;
private String endpoint;
private String region;
}
}

View File

@@ -0,0 +1,72 @@
package com.mystarvindata.vindata.controller;
import com.mystarvindata.vindata.dto.ErrorResp;
import com.mystarvindata.vindata.dto.ResponseBody;
import com.mystarvindata.vindata.service.AdasStatisticsService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* ADAS统计接口对应Python FastAPI路由
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/vehicles/{vin}/adas")
public class AdasStatisticsController {
private final AdasStatisticsService adasStatisticsService;
/**
* GET /vehicles/{vin}/adas/statistics
* 完全对齐Python接口
*/
@GetMapping("/statistics")
public ResponseEntity<?> getAdasStatistics(
// 路径参数
@PathVariable String vin,
// 请求头(必填)
@RequestHeader("Baumuster") String baumuster,
@RequestHeader("Authorization") String authorization,
// 请求头(可选)
@RequestHeader(value = "X-Tracking-Id", required = false) String xTrackingId,
@RequestHeader(value = "X-Application-Name", required = false) String xApplicationName
) {
try {
// 调用业务层
ResponseBody result = adasStatisticsService.getAdasStatistics(vin, baumuster);
// 无数据返回204 + 标准错误体和Python完全一致
if (result == null) {
ErrorResp errorResp = buildErrorResp(0, "NO_CONTENT", "No data found", vin);
return new ResponseEntity<>(errorResp, HttpStatus.NO_CONTENT);
}
// 成功返回200 + 正常数据
return ResponseEntity.ok(result);
} catch (Exception e) {
// 服务器异常返回500 + 标准错误体
ErrorResp errorResp = buildErrorResp(500, "SERVER_ERROR", e.getMessage(), vin);
return new ResponseEntity<>(errorResp, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 构建统一错误响应1:1复刻Python ErrorResp格式
*/
private ErrorResp buildErrorResp(int status, String code, String message, String vin) {
ErrorResp resp = new ErrorResp();
resp.setStatus(status);
resp.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
resp.setCode(code);
resp.setMessage(message);
resp.setDetails(null);
resp.setPath("/vehicles/" + vin + "/adas/statistics");
resp.setData(null);
return resp;
}
}

View File

@@ -0,0 +1,7 @@
package com.mystarvindata.vindata.dto;
public class AdasLevel {
public static final String L2 = "L2";
public static final String L2_PLUS = "L2_PLUS";
public static final String L2_PLUS_PLUS = "L2_PLUS_PLUS";
}

View File

@@ -0,0 +1,10 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
@Data
public class AssistDetail {
private String adasLevel;
private Integer mileage;
private Integer duration;
}

View File

@@ -0,0 +1,16 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
import java.util.List;
/**
* 对应Python中 driving 结构
* 包含:总览(overall) + 辅助驾驶详情(assistedDetails)
*/
@Data
public class Driving {
// 驾驶总览
private DrivingOverall overall;
// 辅助驾驶详情列表
private List<AssistDetail> assistedDetails;
}

View File

@@ -0,0 +1,9 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
@Data
public class DrivingOverall {
private Integer mileage;
private Integer duration;
}

View File

@@ -0,0 +1,14 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
@Data
public class ErrorResp {
private Integer status;
private String timestamp;
private String code;
private String message;
private Object details;
private String path;
private Object data;
}

View File

@@ -0,0 +1,10 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
@Data
public class ParkingInfo {
private String type;
private Integer count;
private Integer averageDuration;
}

View File

@@ -0,0 +1,5 @@
package com.mystarvindata.vindata.dto;
public class ParkingType {
public static final String APA_IN = "APA_IN";
}

View File

@@ -0,0 +1,9 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
import java.util.List;
@Data
public class ParkingWrapper {
private List<ParkingInfo> parking;
}

View File

@@ -0,0 +1,9 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
@Data
public class PeriodStats {
private Driving driving;
private ParkingWrapper parking;
}

View File

@@ -0,0 +1,9 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
@Data
public class ResponseBody {
private Long lastUpdatedAt;
private VehicleStats vehicleStatistics;
}

View File

@@ -0,0 +1,13 @@
package com.mystarvindata.vindata.dto;
import lombok.Data;
@Data
public class VehicleStats {
private String vehicleGeneration;
private Integer mileageUsagePercentile;
private PeriodStats total;
private PeriodStats year;
private PeriodStats month;
private PeriodStats week;
}

View File

@@ -0,0 +1,146 @@
package com.mystarvindata.vindata.service;
import com.mystarvindata.vindata.common.Constants;
import com.mystarvindata.vindata.dto.*;
import com.mystarvindata.vindata.utils.AzureSparkUtils;
import com.mystarvindata.vindata.utils.VinMaskUtils;
import com.mystarvindata.vindata.utils.WeDataUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
@RequiredArgsConstructor
public class AdasStatisticsService {
private final WeDataUtils weDataUtils;
private final AzureSparkUtils azureSparkUtils;
private final VinMaskUtils vinMaskUtils;
public ResponseBody getAdasStatistics(String vin, String baumuster) {
List<Map<String, Object>> vinData;
String vehicleGeneration;
if (baumuster.startsWith("174")) {
vehicleGeneration = "GEN6";
String sql = String.format("SELECT * FROM %s WHERE vin = '%s'", Constants.GEN6_TABLE, vin);
vinData = weDataUtils.queryWeData(sql);
} else {
vehicleGeneration = "GEN5";
String maskedVin = vinMaskUtils.maskVin(vin);
String sql = String.format("SELECT * FROM %s WHERE vin = '%s'", Constants.GEN5_TABLE, maskedVin);
vinData = azureSparkUtils.queryAzureSpark(sql);
}
if (vinData == null || vinData.isEmpty()) {
return null;
}
return transformVehicleStatistics(vinData, vehicleGeneration);
}
private ResponseBody transformVehicleStatistics(List<Map<String, Object>> originalData, String vehicleGeneration) {
Map<String, Map<String, Object>> dataMap = new HashMap<>();
for (Map<String, Object> item : originalData) {
String flag = (String) item.get("flag");
if (flag != null && !flag.isEmpty()) {
dataMap.put(flag, item);
}
}
long lastUpdatedAt = System.currentTimeMillis();
PeriodStats total = buildTimePeriodData(dataMap.get("total"));
PeriodStats year = buildTimePeriodData(dataMap.get("year"));
PeriodStats month = buildTimePeriodData(dataMap.get("month"));
PeriodStats week = buildTimePeriodData(dataMap.get("week"));
VehicleStats vehicleStats = new VehicleStats();
vehicleStats.setVehicleGeneration(vehicleGeneration);
vehicleStats.setMileageUsagePercentile(0);
vehicleStats.setTotal(total);
vehicleStats.setYear(year);
vehicleStats.setMonth(month);
vehicleStats.setWeek(week);
ResponseBody responseBody = new ResponseBody();
responseBody.setLastUpdatedAt(lastUpdatedAt);
responseBody.setVehicleStatistics(vehicleStats);
return responseBody;
}
private PeriodStats buildTimePeriodData(Map<String, Object> item) {
if (item == null) {
item = new HashMap<>();
}
double dtrMileage = parseDouble(item.get("DTR_on_mileage"));
double dtrDuration = parseDouble(item.get("DTR_on_duration"));
double l2Mileage = parseDouble(item.get("L2p_on_mileage"));
double l2Duration = parseDouble(item.get("L2p_on_duration"));
double l2PlusMileage = parseDouble(item.get("L2PP_on_mileage"));
double l2PlusDuration = parseDouble(item.get("L2PP_on_duration"));
int parkingCount = parseInt(item.get("cnt"));
double parkingDuration = parseDouble(item.get("apa_in_dur"));
DrivingOverall overall = new DrivingOverall();
overall.setMileage((int) dtrMileage);
overall.setDuration((int) dtrDuration);
AssistDetail l2 = new AssistDetail();
l2.setAdasLevel(AdasLevel.L2);
l2.setMileage((int) l2Mileage);
l2.setDuration((int) l2Duration);
AssistDetail l2Plus = new AssistDetail();
l2Plus.setAdasLevel(AdasLevel.L2_PLUS);
l2Plus.setMileage((int) l2PlusMileage);
l2Plus.setDuration((int) l2PlusDuration);
AssistDetail l2PlusPlus = new AssistDetail();
l2PlusPlus.setAdasLevel(AdasLevel.L2_PLUS_PLUS);
l2PlusPlus.setMileage(0);
l2PlusPlus.setDuration(0);
List<AssistDetail> assistedDetails = Arrays.asList(l2, l2Plus, l2PlusPlus);
Driving driving = new Driving();
driving.setOverall(overall);
driving.setAssistedDetails(assistedDetails);
ParkingInfo parkingInfo = new ParkingInfo();
parkingInfo.setType(ParkingType.APA_IN);
parkingInfo.setCount(parkingCount);
parkingInfo.setAverageDuration((int) parkingDuration);
ParkingWrapper parkingWrapper = new ParkingWrapper();
parkingWrapper.setParking(Collections.singletonList(parkingInfo));
PeriodStats periodStats = new PeriodStats();
periodStats.setDriving(driving);
periodStats.setParking(parkingWrapper);
return periodStats;
}
private double parseDouble(Object value) {
if (value == null) return 0.0;
try {
return Double.parseDouble(value.toString());
} catch (Exception e) {
return 0.0;
}
}
private int parseInt(Object value) {
if (value == null) return 0;
try {
return Integer.parseInt(value.toString());
} catch (Exception e) {
return 0;
}
}
}

View File

@@ -0,0 +1,74 @@
package com.mystarvindata.vindata.utils;
import com.mystarvindata.vindata.config.CloudConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
@Component
@RequiredArgsConstructor
public class AzureSparkUtils {
private final CloudConfig.AzureDatabricksConfig azureDatabricksConfig;
public List<Map<String, Object>> queryAzureSpark(String sql) {
return queryAzureSpark(sql, null);
}
public List<Map<String, Object>> queryAzureSpark(String sql, List<Object> params) {
List<Map<String, Object>> resultList = new ArrayList<>();
try {
Class.forName("com.databricks.client.jdbc.Driver");
// 🔥 核心修复:自动去除 host 中的 https:// 前缀解决DNS解析失败
String host = azureDatabricksConfig.getHost()
.replace("https://", "") // 移除https
.replace("http://", "") // 移除http
.replace("/", ""); // 移除多余斜杠
// 拼接标准JDBC URL
String jdbcUrl = String.format(
"jdbc:databricks://%s:443/default;httpPath=%s;ssl=1",
host,
azureDatabricksConfig.getHttpPath()
);
// 建立连接
try (Connection conn = DriverManager.getConnection(jdbcUrl, "token", azureDatabricksConfig.getToken());
PreparedStatement pstmt = conn.prepareStatement(sql)) {
if (params != null && !params.isEmpty()) {
for (int i = 0; i < params.size(); i++) {
pstmt.setObject(i + 1, params.get(i));
}
}
// 解析结果
try (ResultSet rs = pstmt.executeQuery()) {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while (rs.next()) {
Map<String, Object> row = new HashMap<>();
for (int i = 1; i <= columnCount; i++) {
row.put(metaData.getColumnLabel(i), rs.getObject(i));
}
resultList.add(row);
}
}
}
} catch (ClassNotFoundException e) {
System.err.println("Azure Databricks 驱动加载失败");
} catch (SQLException e) {
System.err.println("Azure Databricks 连接/查询失败");
e.printStackTrace();
}
return resultList;
}
}

View File

@@ -0,0 +1,65 @@
package com.mystarvindata.vindata.utils;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* VIN掩码工具
*/
@Component
public class VinMaskUtils {
private static final Map<Integer, Integer> MAPPING = new HashMap<>();
static {
// 对应Python的mapping
MAPPING.put(73, 88);
MAPPING.put(79, 89);
MAPPING.put(81, 90);
}
/**
* VIN掩码转换和Python函数完全一样的逻辑
*/
public String maskVin(String realVin) {
if (realVin == null || realVin.length() < 6) {
return realVin;
}
// 取最后6位
String idStr = realVin.substring(realVin.length() - 6);
if (!idStr.matches("\\d+")) {
System.out.println("The last six digits of vin:" + realVin + " are not numbers!");
return realVin;
}
StringBuilder maskedId = new StringBuilder();
int add = 0;
int total = 0;
// 计算总和
for (char c : idStr.toCharArray()) {
total += Integer.parseInt(String.valueOf(c));
}
// 核心转换逻辑
for (char c : idStr.toCharArray()) {
add += total + 2;
int num = Integer.parseInt(String.valueOf(c));
int cValue = num + 65 + (add % 13);
// 替换映射字符
if (MAPPING.containsKey(cValue)) {
cValue = MAPPING.get(cValue);
}
maskedId.append((char) cValue);
}
// 反转 + 拼接
String maskedIdReverse = maskedId.reverse().toString();
return realVin.substring(0, realVin.length() - 6) + maskedIdReverse;
}
}

View File

@@ -0,0 +1,208 @@
package com.mystarvindata.vindata.utils;
import com.mystarvindata.vindata.config.CloudConfig;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.dlc.v20210125.DlcClient;
import com.tencentcloudapi.dlc.v20210125.models.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Slf4j
@Component
@RequiredArgsConstructor
public class WeDataUtils {
private final CloudConfig.WeDataConfig weDataConfig;
private static final long POLL_INTERVAL_MS = 1000;
private static final long MAX_WAIT_MS = 300_000;
public List<Map<String, Object>> queryWeData(String sql, List<Object> params) {
if (params != null && !params.isEmpty()) {
sql = applyParams(sql, params);
}
try {
DlcClient dlcClient = buildDlcClient();
String taskId = submitTask(dlcClient, sql);
log.info("WeData DLC task submitted, taskId={}", taskId);
TaskResultInfo resultInfo = pollTaskResult(dlcClient, taskId);
return parseResultSet(resultInfo);
} catch (Exception e) {
log.error("WeData DLC query failed, sql={}", sql, e);
return new ArrayList<>();
}
}
public List<Map<String, Object>> queryWeData(String sql) {
return queryWeData(sql, null);
}
private DlcClient buildDlcClient() {
Credential credential = new Credential(
weDataConfig.getAccessKey(),
weDataConfig.getSecretKey()
);
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint(
weDataConfig.getEndpoint() != null
? weDataConfig.getEndpoint()
: "dlc.tencentcloudapi.com"
);
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
String region = weDataConfig.getRegion() != null
? weDataConfig.getRegion()
: "ap-shanghai";
return new DlcClient(credential, region, clientProfile);
}
private String submitTask(DlcClient dlcClient, String sql) throws Exception {
String encodedSql = Base64.getEncoder().encodeToString(
sql.getBytes(StandardCharsets.UTF_8)
);
SQLTask sqlTask = new SQLTask();
sqlTask.setSQL(encodedSql);
Task task = new Task();
task.setSparkSQLTask(sqlTask);
// task.setSQLTask(sqlTask);
CreateTaskRequest request = new CreateTaskRequest();
request.setTask(task);
// request.setDatasourceConnectionName("gen6_prod_aiag");
request.setDataEngineName("gen6_prod_aiag");
request.setDatabaseName("dws");
CreateTaskResponse response = dlcClient.CreateTask(request);
return response.getTaskId();
}
private TaskResultInfo pollTaskResult(DlcClient dlcClient, String taskId) throws Exception {
long startTime = System.currentTimeMillis();
while (true) {
DescribeTaskResultRequest request = new DescribeTaskResultRequest();
request.setTaskId(taskId);
request.setMaxResults(1000L);
DescribeTaskResultResponse response = dlcClient.DescribeTaskResult(request);
TaskResultInfo taskInfo = response.getTaskInfo();
Long state = taskInfo.getState();
// 2=success
if (state != null && state == 2L) {
return taskInfo;
}
// -1=failed, -3=cancelled
if (state != null && (state == -1L || state == -3L)) {
throw new RuntimeException(
"WeData DLC task failed, state=" + state
+ ", message=" + taskInfo.getOutputMessage()
);
}
if (System.currentTimeMillis() - startTime > MAX_WAIT_MS) {
throw new RuntimeException(
"WeData DLC task timeout after " + MAX_WAIT_MS + "ms, taskId=" + taskId
);
}
Thread.sleep(POLL_INTERVAL_MS);
}
}
private List<Map<String, Object>> parseResultSet(TaskResultInfo resultInfo) {
List<Map<String, Object>> resultList = new ArrayList<>();
Column[] schema = resultInfo.getResultSchema();
String resultSet = resultInfo.getResultSet();
if (schema == null || resultSet == null || resultSet.isEmpty()) {
return resultList;
}
String[] columnNames = new String[schema.length];
for (int i = 0; i < schema.length; i++) {
columnNames[i] = schema[i].getName();
}
// ResultSet is a JSON string: array of arrays, e.g. [["val1","val2"],["val3","val4"]]
resultSet = resultSet.trim();
if (!resultSet.startsWith("[")) {
return resultList;
}
// Parse the JSON array of arrays manually to avoid extra dependencies
// Format: [["v1","v2"],["v3","v4"]]
List<List<String>> rows = parseJsonArrayOfArrays(resultSet);
for (List<String> row : rows) {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < columnNames.length && i < row.size(); i++) {
map.put(columnNames[i], row.get(i));
}
resultList.add(map);
}
return resultList;
}
private List<List<String>> parseJsonArrayOfArrays(String json) {
List<List<String>> result = new ArrayList<>();
// Use hutool or simple JSON parsing
// The SDK returns format: [["a","b"],["c","d"]]
try {
cn.hutool.json.JSONArray outerArray = new cn.hutool.json.JSONArray(json);
for (int i = 0; i < outerArray.size(); i++) {
cn.hutool.json.JSONArray innerArray = outerArray.getJSONArray(i);
List<String> row = new ArrayList<>();
for (int j = 0; j < innerArray.size(); j++) {
Object val = innerArray.get(j);
row.add(val == null ? null : val.toString());
}
result.add(row);
}
} catch (Exception e) {
log.warn("Failed to parse DLC result set JSON", e);
}
return result;
}
private String applyParams(String sql, List<Object> params) {
StringBuilder sb = new StringBuilder();
int paramIndex = 0;
for (int i = 0; i < sql.length(); i++) {
char c = sql.charAt(i);
if (c == '?' && paramIndex < params.size()) {
Object param = params.get(paramIndex++);
if (param == null) {
sb.append("NULL");
} else if (param instanceof Number) {
sb.append(param);
} else {
sb.append('\'').append(param.toString().replace("'", "''")).append('\'');
}
} else {
sb.append(c);
}
}
return sb.toString();
}
}

View File

@@ -0,0 +1,24 @@
spring:
application:
name: "vindata"
# 服务器端口
server:
port: 8000
# 微软云 Databricks 配置
azure:
databricks:
host: "https://adb-3035612432650494.2.databricks.azure.cn"
token: "dapib91144b39feda0f970fee030a07be1e3"
cluster-id: "1203-054146-wkjf95i0"
http-path: sql/protocolv1/o/3035612432650494/${azure.databricks.cluster-id}
# WeData 配置
wedata:
access-key: "AKIDpW1LTQDoERUI074pPo01rUwIZmqjqrrm"
secret-key: "pStFamneXJuJXaS9gQ0OaZM0UlttjPwJ"
project-id: "2791265636599083008"
region: "ap-shanghai"
data-engine-name: "gen6_prod_aiag"
database-name: "dws"

View File

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