feat: mongo联查一版
This commit is contained in:
commit
33c9563611
|
@ -0,0 +1,2 @@
|
|||
/mvnw text eol=lf
|
||||
*.cmd text eol=crlf
|
|
@ -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/
|
|
@ -0,0 +1,2 @@
|
|||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
|
@ -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.3
|
||||
#
|
||||
# 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 "$@"
|
|
@ -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.3
|
||||
@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"
|
|
@ -0,0 +1,71 @@
|
|||
<?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.18</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>baseMongo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>mongo-simple</name>
|
||||
<description>mongo-simple</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
<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>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,13 @@
|
|||
package org.example.mongosimple;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MongoSimpleApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MongoSimpleApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package org.example.mongosimple.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.example.mongosimple.entity.ShoppingResult;
|
||||
import org.example.mongosimple.prepareEntity.MongoAggregationBuilder;
|
||||
import org.example.mongosimple.prepareEntity.PageResult;
|
||||
import org.example.mongosimple.prepareEntity.ShoppingSearchParams;
|
||||
import org.example.mongosimple.service.ShoppingAggregationService;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MongoDB聚合查询框架演示控制器
|
||||
* @author admin
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test")
|
||||
@RequiredArgsConstructor
|
||||
public class TestController {
|
||||
|
||||
private final ShoppingAggregationService shoppingAggregationService;
|
||||
|
||||
@GetMapping("/facet-pagination")
|
||||
public Map<String, Object> testFacetPagination(
|
||||
@RequestParam(required = false) String phase,
|
||||
@RequestParam(required = false) String user,
|
||||
@RequestParam(required = false) String city,
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false, defaultValue = "0") int page,
|
||||
@RequestParam(required = false, defaultValue = "5") int size) {
|
||||
|
||||
// 构建搜索参数
|
||||
ShoppingSearchParams params =
|
||||
new ShoppingSearchParams()
|
||||
.setPhaseName(phase != null ? phase : "618")
|
||||
.setUsernamePattern(user != null ? user : "test")
|
||||
.setCity(city != null ? city : "dezhou")
|
||||
.setCategoryName(category != null ? category : "电子产品")
|
||||
.setMaxPrice(new BigDecimal(6000))
|
||||
.pageNumber(page, size); // 使用新的pageNumber方法
|
||||
|
||||
// 使用facet优化分页查询
|
||||
PageResult<ShoppingResult> pageResult =
|
||||
shoppingAggregationService.searchWithCriteriaAndPagination(params);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
// 结果数据
|
||||
response.put("results", pageResult.getContent());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package org.example.mongosimple.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ShoppingResult {
|
||||
private String username;
|
||||
private String email;
|
||||
private String productName;
|
||||
private BigDecimal price;
|
||||
private String categoryName;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package org.example.mongosimple.initializer;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.example.mongosimple.mongoObject.*;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DataInitializer implements CommandLineRunner {
|
||||
|
||||
private final MongoTemplate mongoTemplate;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
// 检查是否已经初始化过数据
|
||||
/* if (userRepository.count() > 0) {
|
||||
System.out.println("数据已存在,跳过初始化");
|
||||
return;
|
||||
}*/
|
||||
|
||||
// System.out.println("开始初始化测试数据...");
|
||||
|
||||
// 初始化用户数据
|
||||
// initUsers();
|
||||
|
||||
|
||||
// System.out.println("测试数据初始化完成!");
|
||||
}
|
||||
|
||||
private void initUsers() {
|
||||
// System.out.println("初始化用户数据...");
|
||||
|
||||
Address address = new Address();
|
||||
address.setCity("jinan");
|
||||
|
||||
Address address1 = new Address();
|
||||
address1.setCity("dezhou");
|
||||
|
||||
mongoTemplate.save(address);
|
||||
mongoTemplate.save(address1);
|
||||
|
||||
// 创建用户1
|
||||
User user1 = new User("test1", "test1@example.com", "123456", "68c4f94d7757c98d8116c6a4");
|
||||
User user2 = new User("test2", "test2@example.com", "123456", "68c4f94d7757c98d8116c6a4");
|
||||
User user3 = new User("test3", "test3@example.com", "123456", "68c4f94d7757c98d8116c6a5");
|
||||
|
||||
List<User> users = new ArrayList<>();
|
||||
users.add(user1);
|
||||
users.add(user2);
|
||||
users.add(user3);
|
||||
// mongoTemplate.insert(user1);
|
||||
// mongoTemplate.insertAll(users);
|
||||
ShoppingPhase shoppingPhase = new ShoppingPhase();
|
||||
shoppingPhase.setName("双十一");
|
||||
|
||||
ShoppingPhase shoppingPhase1 = new ShoppingPhase();
|
||||
shoppingPhase1.setName("618");
|
||||
// mongoTemplate.save(shoppingPhase);
|
||||
// mongoTemplate.save(shoppingPhase1);
|
||||
|
||||
Category category = new Category();
|
||||
category.setName("电子产品");
|
||||
Category category1 = new Category();
|
||||
category1.setName("家具");
|
||||
// mongoTemplate.save(category);
|
||||
// mongoTemplate.save(category1);
|
||||
|
||||
Product product = new Product("iPhone 17", new BigDecimal("5999.00"), "68c4fbe9b9de8975d97681b9");
|
||||
Product product1 = new Product("iPhone 17 pro", new BigDecimal("8999.00"), "68c4fbe9b9de8975d97681b9");
|
||||
Product product2 = new Product("mac mini", new BigDecimal("4299.00"), "68c4fbe9b9de8975d97681ba");
|
||||
mongoTemplate.save(product);
|
||||
mongoTemplate.save(product1);
|
||||
mongoTemplate.save(product2);
|
||||
|
||||
// 来自的dezhou的用户 在618期间买的 价格位于 5000 - 6000 的电子产品z
|
||||
|
||||
ShoppingRecord shoppingRecord = new ShoppingRecord("68c4fabe669d40228340eaa9", "68c4fcd9f53fc172ab23f4ac", "68c4fb3a370a3428ec124244");
|
||||
ShoppingRecord shoppingRecord1 = new ShoppingRecord("68c4fabe669d40228340eaab", "68c4fcd9f53fc172ab23f4ac", "68c4fb3a370a3428ec124244");
|
||||
ShoppingRecord shoppingRecord2 = new ShoppingRecord("68c4fabe669d40228340eaa9", "68c4fcd9f53fc172ab23f4ad", "68c4fb3a370a3428ec124244");
|
||||
ShoppingRecord shoppingRecord3 = new ShoppingRecord("68c4fabe669d40228340eaa9", "68c4fcd9f53fc172ab23f4ac", "68c4fb9775d63063d25ed01d");
|
||||
ShoppingRecord shoppingRecord4 = new ShoppingRecord("68c4fabe669d40228340eaa9", "68c4fcd9f53fc172ab23f4ae", "68c4fb3a370a3428ec124244");
|
||||
|
||||
|
||||
/*mongoTemplate.save(shoppingRecord);
|
||||
mongoTemplate.save(shoppingRecord1);
|
||||
mongoTemplate.save(shoppingRecord2);
|
||||
mongoTemplate.save(shoppingRecord3);
|
||||
mongoTemplate.save(shoppingRecord4);*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.example.mongosimple.mongoObject;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
*/
|
||||
@Data
|
||||
@Document(collection = "address")
|
||||
public class Address {
|
||||
@Id
|
||||
private String id;
|
||||
private String city;
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package org.example.mongosimple.mongoObject;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@Data
|
||||
@Document(collection = "categories")
|
||||
public class Category {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
|
||||
private String name;
|
||||
|
||||
|
||||
private String description;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package org.example.mongosimple.mongoObject;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
@Document(collection = "product")
|
||||
public class Product {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
private String categoryId;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public Product(String name, BigDecimal price, String categoryId) {
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.categoryId = categoryId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package org.example.mongosimple.mongoObject;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
*/
|
||||
@Data
|
||||
@Document(collection = "shopping_phase")
|
||||
public class ShoppingPhase {
|
||||
@Id
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package org.example.mongosimple.mongoObject;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@Document(collection = "shopping_record")
|
||||
public class ShoppingRecord {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private String userId;
|
||||
|
||||
private String productId;
|
||||
|
||||
private String shoppingPhaseId;
|
||||
|
||||
public ShoppingRecord(String userId, String productId, String shoppingPhaseId) {
|
||||
this.userId = userId;
|
||||
this.productId = productId;
|
||||
this.shoppingPhaseId = shoppingPhaseId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.example.mongosimple.mongoObject;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Document(collection = "users")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String email;
|
||||
|
||||
private String phone;
|
||||
|
||||
private String addressId;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public User(String username, String email, String phone, String addressId) {
|
||||
this.username = username;
|
||||
this.email = email;
|
||||
this.phone = phone;
|
||||
this.addressId = addressId;
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
package org.example.mongosimple.prepareEntity;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 动态Criteria构建器 - 根据字段是否为空动态拼接查询条件
|
||||
*
|
||||
* @author admin
|
||||
*/
|
||||
public class CriteriaBuilder {
|
||||
|
||||
private final List<Criteria> criteriaList;
|
||||
|
||||
public CriteriaBuilder() {
|
||||
this.criteriaList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的Criteria构建器
|
||||
*/
|
||||
public static CriteriaBuilder create() {
|
||||
return new CriteriaBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串不为空时添加等值条件
|
||||
*/
|
||||
public CriteriaBuilder whenNotEmpty(String value, String field) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
criteriaList.add(Criteria.where(field).is(value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象不为空时添加等值条件
|
||||
*/
|
||||
public CriteriaBuilder whenNotNull(Object value, String field) {
|
||||
if (value != null) {
|
||||
criteriaList.add(Criteria.where(field).is(value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串不为空时添加模糊匹配条件
|
||||
*/
|
||||
public CriteriaBuilder whenNotEmptyLike(String value, String field) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
criteriaList.add(Criteria.where(field).regex(".*" + value + ".*", "i"));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串不为空时添加正则表达式条件
|
||||
*/
|
||||
public CriteriaBuilder whenNotEmptyRegex(String value, String field, String pattern) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
criteriaList.add(Criteria.where(field).regex(pattern.replace("{value}", value), "i"));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数值范围条件(最小值不为空时)
|
||||
*/
|
||||
public CriteriaBuilder whenMinValue(Number minValue, String field) {
|
||||
if (minValue != null) {
|
||||
criteriaList.add(Criteria.where(field).gte(minValue));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数值范围条件(最大值不为空时)
|
||||
*/
|
||||
public CriteriaBuilder whenMaxValue(Number maxValue, String field) {
|
||||
if (maxValue != null) {
|
||||
criteriaList.add(Criteria.where(field).lte(maxValue));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数值范围条件(最小值和最大值)
|
||||
*/
|
||||
public CriteriaBuilder whenRange(Number minValue, Number maxValue, String field) {
|
||||
if (minValue != null && maxValue != null) {
|
||||
criteriaList.add(Criteria.where(field).gte(minValue).lte(maxValue));
|
||||
} else if (minValue != null) {
|
||||
criteriaList.add(Criteria.where(field).gte(minValue));
|
||||
} else if (maxValue != null) {
|
||||
criteriaList.add(Criteria.where(field).lte(maxValue));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期范围条件
|
||||
*/
|
||||
public CriteriaBuilder whenDateRange(LocalDateTime startDate, LocalDateTime endDate, String field) {
|
||||
if (startDate != null && endDate != null) {
|
||||
criteriaList.add(Criteria.where(field).gte(startDate).lte(endDate));
|
||||
} else if (startDate != null) {
|
||||
criteriaList.add(Criteria.where(field).gte(startDate));
|
||||
} else if (endDate != null) {
|
||||
criteriaList.add(Criteria.where(field).lte(endDate));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合不为空时添加in条件
|
||||
*/
|
||||
public CriteriaBuilder whenNotEmptyIn(Collection<?> values, String field) {
|
||||
if (values != null && !values.isEmpty()) {
|
||||
criteriaList.add(Criteria.where(field).in(values));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组不为空时添加in条件
|
||||
*/
|
||||
public CriteriaBuilder whenNotEmptyIn(Object[] values, String field) {
|
||||
if (values != null && values.length > 0) {
|
||||
criteriaList.add(Criteria.where(field).in(values));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 布尔值不为空时添加条件
|
||||
*/
|
||||
public CriteriaBuilder whenNotNull(Boolean value, String field) {
|
||||
if (value != null) {
|
||||
criteriaList.add(Criteria.where(field).is(value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义条件判断
|
||||
*/
|
||||
public CriteriaBuilder when(boolean condition, Function<Criteria, Criteria> criteriaFunction, String field) {
|
||||
if (condition) {
|
||||
criteriaList.add(criteriaFunction.apply(Criteria.where(field)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义条件判断(带值)
|
||||
*/
|
||||
public <T> CriteriaBuilder when(T value, Predicate<T> condition, Function<T, Criteria> criteriaFunction) {
|
||||
if (value != null && condition.test(value)) {
|
||||
criteriaList.add(criteriaFunction.apply(value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段存在性检查
|
||||
*/
|
||||
public CriteriaBuilder whenFieldExists(Boolean checkExists, String field) {
|
||||
if (checkExists != null) {
|
||||
if (checkExists) {
|
||||
criteriaList.add(Criteria.where(field).exists(true));
|
||||
} else {
|
||||
criteriaList.add(Criteria.where(field).exists(false));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段不为null检查
|
||||
*/
|
||||
public CriteriaBuilder whenFieldNotNull(Boolean checkNotNull, String field) {
|
||||
if (checkNotNull != null) {
|
||||
if (checkNotNull) {
|
||||
criteriaList.add(Criteria.where(field).ne(null));
|
||||
} else {
|
||||
criteriaList.add(Criteria.where(field).is(null));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义Criteria
|
||||
*/
|
||||
public CriteriaBuilder addCriteria(Criteria criteria) {
|
||||
if (criteria != null) {
|
||||
criteriaList.add(criteria);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件性添加自定义Criteria
|
||||
*/
|
||||
public CriteriaBuilder addCriteriaIf(boolean condition, Criteria criteria) {
|
||||
if (condition && criteria != null) {
|
||||
criteriaList.add(criteria);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建最终的Criteria(AND逻辑)
|
||||
*/
|
||||
public Criteria buildAnd() {
|
||||
if (criteriaList.isEmpty()) {
|
||||
return new Criteria();
|
||||
}
|
||||
if (criteriaList.size() == 1) {
|
||||
return criteriaList.get(0);
|
||||
}
|
||||
return new Criteria().andOperator(criteriaList.toArray(new Criteria[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建最终的Criteria(OR逻辑)
|
||||
*/
|
||||
public Criteria buildOr() {
|
||||
if (criteriaList.isEmpty()) {
|
||||
return new Criteria();
|
||||
}
|
||||
if (criteriaList.size() == 1) {
|
||||
return criteriaList.get(0);
|
||||
}
|
||||
return new Criteria().orOperator(criteriaList.toArray(new Criteria[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取条件数量
|
||||
*/
|
||||
public int size() {
|
||||
return criteriaList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有条件
|
||||
*/
|
||||
public boolean hasConditions() {
|
||||
return !criteriaList.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有条件
|
||||
*/
|
||||
public CriteriaBuilder clear() {
|
||||
criteriaList.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有条件列表
|
||||
*/
|
||||
public List<Criteria> getCriteriaList() {
|
||||
return new ArrayList<>(criteriaList);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package org.example.mongosimple.prepareEntity;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 字段映射类 - 手动配置投影和排序字段映射
|
||||
* @author admin
|
||||
*/
|
||||
public class FieldMapping {
|
||||
// 投影字段映射:原始字段路径 -> 投影后字段名
|
||||
private final Map<String, String> projectionMapping = new HashMap<>();
|
||||
|
||||
// 排序字段映射:用户输入字段 -> 实际排序字段
|
||||
private final Map<String, String> sortMapping = new HashMap<>();
|
||||
|
||||
// 默认包含的字段
|
||||
private String[] defaultIncludeFields = {"_id", "createdAt"};
|
||||
|
||||
/**
|
||||
* 空构造函数 - 需要手动添加字段映射
|
||||
*/
|
||||
public FieldMapping() {
|
||||
// 空构造,需要手动添加映射
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认包含字段
|
||||
*/
|
||||
public FieldMapping setDefaultIncludeFields(String... fields) {
|
||||
this.defaultIncludeFields = fields;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加投影字段映射
|
||||
* @param originalField 原始字段路径,如 "user.username"
|
||||
* @param projectedField 投影后字段名,如 "username"
|
||||
*/
|
||||
public FieldMapping addProjectionMapping(String originalField, String projectedField) {
|
||||
projectionMapping.put(originalField, projectedField);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加投影字段映射
|
||||
*/
|
||||
public FieldMapping addProjectionMappings(Map<String, String> mappings) {
|
||||
projectionMapping.putAll(mappings);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加排序字段映射
|
||||
* @param sortField 排序字段(可以是原始字段或投影后字段)
|
||||
* @param actualField 实际排序使用的字段名
|
||||
*/
|
||||
public FieldMapping addSortMapping(String sortField, String actualField) {
|
||||
sortMapping.put(sortField, actualField);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加排序字段映射
|
||||
*/
|
||||
public FieldMapping addSortMappings(Map<String, String> mappings) {
|
||||
sortMapping.putAll(mappings);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建投影操作函数 - 包含所有已配置的字段映射
|
||||
*/
|
||||
public Function<ProjectionOperation, ProjectionOperation> createProjectionFunction() {
|
||||
return projection -> {
|
||||
// 先包含默认字段
|
||||
ProjectionOperation result = projection.andInclude(defaultIncludeFields);
|
||||
|
||||
// 添加所有配置的字段映射
|
||||
for (Map.Entry<String, String> entry : projectionMapping.entrySet()) {
|
||||
result = result.and(entry.getKey()).as(entry.getValue());
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建选择性投影操作函数 - 只包含指定的字段
|
||||
*/
|
||||
public Function<ProjectionOperation, ProjectionOperation> createSelectiveProjectionFunction(String... selectedFields) {
|
||||
return projection -> {
|
||||
// 先包含默认字段
|
||||
ProjectionOperation result = projection.andInclude(defaultIncludeFields);
|
||||
|
||||
// 只添加选中的字段
|
||||
for (String field : selectedFields) {
|
||||
if (projectionMapping.containsKey(field)) {
|
||||
result = result.and(field).as(projectionMapping.get(field));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取兼容的排序字段名
|
||||
* @param originalSortField 原始排序字段
|
||||
* @param defaultField 默认排序字段(如果找不到映射)
|
||||
*/
|
||||
public String getCompatibleSortField(String originalSortField, String defaultField) {
|
||||
if (originalSortField == null) {
|
||||
return defaultField != null ? defaultField : "createdAt";
|
||||
}
|
||||
|
||||
return sortMapping.getOrDefault(originalSortField, defaultField != null ? defaultField : originalSortField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字段是否支持排序
|
||||
*/
|
||||
public boolean isSortFieldSupported(String sortField) {
|
||||
return sortMapping.containsKey(sortField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取投影后的字段名
|
||||
*/
|
||||
public String getProjectedFieldName(String originalField) {
|
||||
return projectionMapping.getOrDefault(originalField, originalField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有配置的映射
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return projectionMapping.isEmpty() && sortMapping.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取映射统计信息
|
||||
*/
|
||||
public String getStats() {
|
||||
return String.format("投影映射: %d个, 排序映射: %d个",
|
||||
projectionMapping.size(), sortMapping.size());
|
||||
}
|
||||
|
||||
public String getSortField(String sortField) {
|
||||
return sortMapping.getOrDefault(sortField, sortField);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,657 @@
|
|||
package org.example.mongosimple.prepareEntity;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.aggregation.*;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* MongoDB聚合查询构建器 - 提供流式API构建复杂聚合查询
|
||||
*
|
||||
* 特性:
|
||||
* - 链式调用,代码简洁
|
||||
* - 自动处理ObjectId转换
|
||||
* - 支持动态条件构建
|
||||
* - 内置常用查询模式
|
||||
*
|
||||
* @author admin
|
||||
*/
|
||||
public class MongoAggregationBuilder {
|
||||
|
||||
private final MongoTemplate mongoTemplate;
|
||||
private final List<AggregationOperation> operations;
|
||||
private final String sourceCollection;
|
||||
|
||||
public MongoAggregationBuilder(MongoTemplate mongoTemplate, String sourceCollection) {
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
this.sourceCollection = sourceCollection;
|
||||
this.operations = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建聚合构建器
|
||||
*/
|
||||
public static MongoAggregationBuilder from(MongoTemplate mongoTemplate, String collection) {
|
||||
return new MongoAggregationBuilder(mongoTemplate, collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加匹配条件
|
||||
*/
|
||||
public MongoAggregationBuilder match(Criteria criteria) {
|
||||
operations.add(Aggregation.match(criteria));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加匹配条件 - 便捷方法
|
||||
*/
|
||||
public MongoAggregationBuilder match(String field, Object value) {
|
||||
return match(Criteria.where(field).is(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加范围匹配
|
||||
*/
|
||||
public MongoAggregationBuilder matchRange(String field, Object min, Object max) {
|
||||
return match(Criteria.where(field).gte(min).lte(max));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加正则匹配
|
||||
*/
|
||||
public MongoAggregationBuilder matchRegex(String field, String regex) {
|
||||
return match(Criteria.where(field).regex(regex, "i"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件性添加Criteria匹配条件
|
||||
*/
|
||||
public MongoAggregationBuilder matchIf(boolean condition, Criteria criteria) {
|
||||
if (condition && criteria != null) {
|
||||
operations.add(Aggregation.match(criteria));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用CriteriaBuilder添加动态匹配条件
|
||||
*/
|
||||
public MongoAggregationBuilder matchDynamic(Function<CriteriaBuilder, CriteriaBuilder> builderFunction) {
|
||||
CriteriaBuilder builder = builderFunction.apply(CriteriaBuilder.create());
|
||||
if (builder.hasConditions()) {
|
||||
operations.add(Aggregation.match(builder.buildAnd()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联查询 - 自动处理ObjectId转换
|
||||
*/
|
||||
public MongoAggregationBuilder lookup(String fromCollection, String localField, String foreignField, String as) {
|
||||
// 自动添加ObjectId转换
|
||||
String convertedField = localField + "Obj";
|
||||
operations.add(AddFieldsOperation.builder()
|
||||
.addField(convertedField)
|
||||
.withValue(ConvertOperators.ToObjectId.toObjectId("$" + localField))
|
||||
.build());
|
||||
|
||||
// 执行lookup
|
||||
operations.add(LookupOperation.newLookup()
|
||||
.from(fromCollection)
|
||||
.localField(convertedField)
|
||||
.foreignField(foreignField)
|
||||
.as(as));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联查询 - 不进行ObjectId转换(用于已经是ObjectId的字段)
|
||||
*/
|
||||
public MongoAggregationBuilder lookupDirect(String fromCollection, String localField, String foreignField, String as) {
|
||||
operations.add(LookupOperation.newLookup()
|
||||
.from(fromCollection)
|
||||
.localField(localField)
|
||||
.foreignField(foreignField)
|
||||
.as(as));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开数组字段
|
||||
*/
|
||||
public MongoAggregationBuilder unwind(String field) {
|
||||
operations.add(Aggregation.unwind(field));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联并展开 - 常用组合操作
|
||||
*/
|
||||
public MongoAggregationBuilder lookupAndUnwind(String fromCollection, String localField, String foreignField, String as) {
|
||||
return lookup(fromCollection, localField, foreignField, as).unwind(as);
|
||||
}
|
||||
|
||||
/**
|
||||
* 投影字段
|
||||
*/
|
||||
public MongoAggregationBuilder project(String... fields) {
|
||||
ProjectionOperation projection = Aggregation.project(fields);
|
||||
operations.add(projection);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义投影
|
||||
*/
|
||||
public MongoAggregationBuilder project(Function<ProjectionOperation, ProjectionOperation> projectionBuilder) {
|
||||
ProjectionOperation projection = projectionBuilder.apply(Aggregation.project());
|
||||
operations.add(projection);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分组聚合
|
||||
*/
|
||||
public MongoAggregationBuilder group(String... fields) {
|
||||
operations.add(Aggregation.group(fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义分组
|
||||
*/
|
||||
public MongoAggregationBuilder group(Function<GroupOperation, GroupOperation> groupBuilder) {
|
||||
GroupOperation group = groupBuilder.apply(Aggregation.group());
|
||||
operations.add(group);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
public MongoAggregationBuilder sort(String field, boolean ascending) {
|
||||
operations.add(Aggregation.sort(ascending ?
|
||||
org.springframework.data.domain.Sort.Direction.ASC :
|
||||
org.springframework.data.domain.Sort.Direction.DESC, field));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制结果数量
|
||||
*/
|
||||
public MongoAggregationBuilder limit(int count) {
|
||||
operations.add(Aggregation.limit(count));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过指定数量
|
||||
*/
|
||||
public MongoAggregationBuilder skip(int count) {
|
||||
operations.add(Aggregation.skip(count));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
public MongoAggregationBuilder page(int pageNumber, int pageSize) {
|
||||
return skip(pageNumber * pageSize).limit(pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义操作
|
||||
*/
|
||||
public MongoAggregationBuilder addOperation(AggregationOperation operation) {
|
||||
operations.add(operation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加多个自定义聚合操作
|
||||
*/
|
||||
public MongoAggregationBuilder addOperations(List<AggregationOperation> operationList) {
|
||||
operations.addAll(operationList);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行聚合查询并返回指定类型结果
|
||||
*/
|
||||
public <T> List<T> execute(Class<T> resultClass) {
|
||||
Aggregation aggregation = Aggregation.newAggregation(operations);
|
||||
return mongoTemplate.aggregate(aggregation, sourceCollection, resultClass).getMappedResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行聚合查询并返回Map结果
|
||||
*/
|
||||
public List<Map> executeAsMap() {
|
||||
return execute(Map.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取构建的聚合操作列表(用于调试)
|
||||
*/
|
||||
public List<AggregationOperation> getOperations() {
|
||||
return new ArrayList<>(operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行分页查询 - 使用facet优化,一次查询获取总数和分页数据
|
||||
* Spring Boot 2/3兼容版本
|
||||
*/
|
||||
public <T> PageResult<T> executeWithPagination(Class<T> resultClass, int page, int size) {
|
||||
try {
|
||||
// 尝试使用facet分页(Spring Boot 3风格)
|
||||
return executeWithFacetPagination(resultClass, page, size);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Facet分页失败,使用传统分页: " + e.getMessage());
|
||||
// 降级到传统分页(Spring Boot 2兼容)
|
||||
return executeWithTraditionalPagination(resultClass, page, size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Facet分页实现
|
||||
*/
|
||||
private <T> PageResult<T> executeWithFacetPagination(Class<T> resultClass, int page, int size) {
|
||||
// 创建facet操作
|
||||
FacetOperation facetOperation = Aggregation.facet(
|
||||
// 计算总数的管道
|
||||
Aggregation.count().as("total")
|
||||
).as("totalCount").and(
|
||||
// 分页数据的管道
|
||||
Aggregation.skip((long) page * size),
|
||||
Aggregation.limit(size)
|
||||
).as("pagedResults");
|
||||
|
||||
// 添加facet操作到现有管道
|
||||
List<AggregationOperation> facetOperations = new ArrayList<>(operations);
|
||||
facetOperations.add(facetOperation);
|
||||
|
||||
Aggregation aggregation = Aggregation.newAggregation(facetOperations);
|
||||
AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, sourceCollection, Map.class);
|
||||
|
||||
return parseFacetResults(results, resultClass, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 传统分页实现 - Spring Boot 2兼容
|
||||
*/
|
||||
private <T> PageResult<T> executeWithTraditionalPagination(Class<T> resultClass, int page, int size) {
|
||||
// 1. 获取总数
|
||||
List<AggregationOperation> countOperations = new ArrayList<>(operations);
|
||||
countOperations.add(Aggregation.count().as("total"));
|
||||
|
||||
Aggregation countAggregation = Aggregation.newAggregation(countOperations);
|
||||
AggregationResults<Map> countResults = mongoTemplate.aggregate(countAggregation, sourceCollection, Map.class);
|
||||
|
||||
long total = 0;
|
||||
if (!countResults.getMappedResults().isEmpty()) {
|
||||
Map<String, Object> countResult = countResults.getMappedResults().get(0);
|
||||
Object totalObj = countResult.get("total");
|
||||
if (totalObj instanceof Number) {
|
||||
total = ((Number) totalObj).longValue();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 获取分页数据
|
||||
List<AggregationOperation> dataOperations = new ArrayList<>(operations);
|
||||
dataOperations.add(Aggregation.skip((long) page * size));
|
||||
dataOperations.add(Aggregation.limit(size));
|
||||
|
||||
Aggregation dataAggregation = Aggregation.newAggregation(dataOperations);
|
||||
AggregationResults<T> dataResults = mongoTemplate.aggregate(dataAggregation, sourceCollection, resultClass);
|
||||
|
||||
return new PageResult<>(dataResults.getMappedResults(), total, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行分页查询并返回Map结果
|
||||
*/
|
||||
public PageResult<Map> executeWithPaginationAsMap(int page, int size) {
|
||||
return executeWithPagination(Map.class, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析facet查询结果
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> PageResult<T> parseFacetResults(AggregationResults<Map> results, Class<T> resultClass, int page, int size) {
|
||||
if (results.getMappedResults().isEmpty()) {
|
||||
return new PageResult<>(Collections.emptyList(), 0, page, size);
|
||||
}
|
||||
|
||||
Map<String, Object> facetResult = results.getMappedResults().get(0);
|
||||
|
||||
// 解析总数
|
||||
long total = 0;
|
||||
List<Map<String, Object>> totalCountList = (List<Map<String, Object>>) facetResult.get("totalCount");
|
||||
if (totalCountList != null && !totalCountList.isEmpty()) {
|
||||
Object totalObj = totalCountList.get(0).get("total");
|
||||
if (totalObj instanceof Number) {
|
||||
total = ((Number) totalObj).longValue();
|
||||
}
|
||||
}
|
||||
|
||||
// 解析分页数据
|
||||
List<T> content = new ArrayList<>();
|
||||
List<Map<String, Object>> pagedResultsList = (List<Map<String, Object>>) facetResult.get("pagedResults");
|
||||
if (pagedResultsList != null) {
|
||||
if (resultClass == Map.class) {
|
||||
content = (List<T>) pagedResultsList;
|
||||
} else {
|
||||
// 转换为目标类型
|
||||
for (Map<String, Object> item : pagedResultsList) {
|
||||
T convertedItem = mongoTemplate.getConverter().read(resultClass, new BasicDBObject(item));
|
||||
content.add(convertedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PageResult<>(content, total, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合统计查询 - 使用facet同时执行多种统计
|
||||
*/
|
||||
public Map<String, Object> executeWithStatistics(String... statisticTypes) {
|
||||
// 创建facet操作,正确的API使用方式
|
||||
FacetOperation facetOperation = null;
|
||||
|
||||
if (statisticTypes.length > 0) {
|
||||
// 开始构建facet
|
||||
String firstType = statisticTypes[0].toLowerCase();
|
||||
switch (firstType) {
|
||||
case "count":
|
||||
facetOperation = Aggregation.facet(Aggregation.count().as("total")).as("count");
|
||||
break;
|
||||
case "sum":
|
||||
facetOperation = Aggregation.facet(
|
||||
Aggregation.group().sum("product.price").as("totalAmount")
|
||||
).as("sum");
|
||||
break;
|
||||
case "avg":
|
||||
facetOperation = Aggregation.facet(
|
||||
Aggregation.group().avg("product.price").as("avgPrice")
|
||||
).as("avg");
|
||||
break;
|
||||
case "minmax":
|
||||
facetOperation = Aggregation.facet(
|
||||
Aggregation.group()
|
||||
.min("product.price").as("minPrice")
|
||||
.max("product.price").as("maxPrice")
|
||||
).as("minmax");
|
||||
break;
|
||||
case "groupby":
|
||||
facetOperation = Aggregation.facet(
|
||||
Aggregation.group("category.name")
|
||||
.count().as("count")
|
||||
.sum("product.price").as("totalAmount"),
|
||||
Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "count")
|
||||
).as("groupby");
|
||||
break;
|
||||
default:
|
||||
facetOperation = Aggregation.facet(Aggregation.count().as("total")).as("count");
|
||||
}
|
||||
|
||||
// 添加其余的统计类型
|
||||
for (int i = 1; i < statisticTypes.length; i++) {
|
||||
String type = statisticTypes[i].toLowerCase();
|
||||
switch (type) {
|
||||
case "count":
|
||||
facetOperation = facetOperation.and(Aggregation.count().as("total")).as("count");
|
||||
break;
|
||||
case "sum":
|
||||
facetOperation = facetOperation.and(
|
||||
Aggregation.group().sum("product.price").as("totalAmount")
|
||||
).as("sum");
|
||||
break;
|
||||
case "avg":
|
||||
facetOperation = facetOperation.and(
|
||||
Aggregation.group().avg("product.price").as("avgPrice")
|
||||
).as("avg");
|
||||
break;
|
||||
case "minmax":
|
||||
facetOperation = facetOperation.and(
|
||||
Aggregation.group()
|
||||
.min("product.price").as("minPrice")
|
||||
.max("product.price").as("maxPrice")
|
||||
).as("minmax");
|
||||
break;
|
||||
case "groupby":
|
||||
facetOperation = facetOperation.and(
|
||||
Aggregation.group("category.name")
|
||||
.count().as("count")
|
||||
.sum("product.price").as("totalAmount"),
|
||||
Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "count")
|
||||
).as("groupby");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 默认统计
|
||||
facetOperation = Aggregation.facet(Aggregation.count().as("total")).as("count");
|
||||
}
|
||||
|
||||
List<AggregationOperation> statOperations = new ArrayList<>(operations);
|
||||
statOperations.add(facetOperation);
|
||||
|
||||
Aggregation aggregation = Aggregation.newAggregation(statOperations);
|
||||
AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, sourceCollection, Map.class);
|
||||
|
||||
return results.getMappedResults().isEmpty() ?
|
||||
Collections.emptyMap() : results.getMappedResults().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化的统计查询 - 分别执行不同的统计操作
|
||||
* Spring Boot 2/3兼容版本
|
||||
*/
|
||||
public Map<String, Object> executeWithSimpleStatistics() {
|
||||
Map<String, Object> statistics = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 1. 总数统计
|
||||
List<AggregationOperation> countOps = new ArrayList<>(operations);
|
||||
countOps.add(Aggregation.count().as("total"));
|
||||
Aggregation countAgg = Aggregation.newAggregation(countOps);
|
||||
AggregationResults<Map> countResults = mongoTemplate.aggregate(countAgg, sourceCollection, Map.class);
|
||||
if (!countResults.getMappedResults().isEmpty()) {
|
||||
statistics.put("count", countResults.getMappedResults().get(0));
|
||||
}
|
||||
|
||||
// 2. 价格统计 - 使用兼容的字段引用
|
||||
try {
|
||||
List<AggregationOperation> priceOps = new ArrayList<>(operations);
|
||||
priceOps.add(Aggregation.group()
|
||||
.sum("product.price").as("totalAmount")
|
||||
.avg("product.price").as("avgPrice")
|
||||
.min("product.price").as("minPrice")
|
||||
.max("product.price").as("maxPrice"));
|
||||
Aggregation priceAgg = Aggregation.newAggregation(priceOps);
|
||||
AggregationResults<Map> priceResults = mongoTemplate.aggregate(priceAgg, sourceCollection, Map.class);
|
||||
if (!priceResults.getMappedResults().isEmpty()) {
|
||||
statistics.put("priceStats", priceResults.getMappedResults().get(0));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("价格统计失败,尝试备用字段: " + e.getMessage());
|
||||
// 备用方案:使用不同的字段引用方式
|
||||
try {
|
||||
List<AggregationOperation> priceOps = new ArrayList<>(operations);
|
||||
priceOps.add(Aggregation.group()
|
||||
.sum("price").as("totalAmount")
|
||||
.avg("price").as("avgPrice")
|
||||
.min("price").as("minPrice")
|
||||
.max("price").as("maxPrice"));
|
||||
Aggregation priceAgg = Aggregation.newAggregation(priceOps);
|
||||
AggregationResults<Map> priceResults = mongoTemplate.aggregate(priceAgg, sourceCollection, Map.class);
|
||||
if (!priceResults.getMappedResults().isEmpty()) {
|
||||
statistics.put("priceStats", priceResults.getMappedResults().get(0));
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
statistics.put("priceStatsError", "价格统计失败: " + e2.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 分类统计 - 使用兼容的字段引用
|
||||
try {
|
||||
List<AggregationOperation> categoryOps = new ArrayList<>(operations);
|
||||
categoryOps.add(Aggregation.group("category.name")
|
||||
.count().as("count")
|
||||
.sum("product.price").as("totalAmount"));
|
||||
categoryOps.add(Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "count"));
|
||||
categoryOps.add(Aggregation.limit(10));
|
||||
Aggregation categoryAgg = Aggregation.newAggregation(categoryOps);
|
||||
AggregationResults<Map> categoryResults = mongoTemplate.aggregate(categoryAgg, sourceCollection, Map.class);
|
||||
statistics.put("categoryStats", categoryResults.getMappedResults());
|
||||
} catch (Exception e) {
|
||||
System.err.println("分类统计失败: " + e.getMessage());
|
||||
statistics.put("categoryStatsError", "分类统计失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("统计查询执行失败: " + e.getMessage());
|
||||
statistics.put("error", e.getMessage());
|
||||
}
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring Boot 2兼容的构建方法
|
||||
* 自动添加字段展平投影
|
||||
*/
|
||||
public static MongoAggregationBuilder fromWithCompatibility(MongoTemplate mongoTemplate, String sourceCollection) {
|
||||
MongoAggregationBuilder builder = new MongoAggregationBuilder(mongoTemplate, sourceCollection);
|
||||
|
||||
// 检查是否需要兼容性处理
|
||||
if (!SpringBootCompatibilityHelper.supportsNestedFieldReferences()) {
|
||||
System.out.println("检测到Spring Boot 2环境,启用兼容性模式");
|
||||
// 在后续的lookup操作后自动添加字段展平
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容性lookup操作 - 自动添加字段展平
|
||||
*/
|
||||
public MongoAggregationBuilder lookupAndUnwindWithCompatibility(String from, String localField, String foreignField, String as) {
|
||||
// 执行标准的lookup和unwind
|
||||
this.lookupAndUnwind(from, localField, foreignField, as);
|
||||
|
||||
// 如果是Spring Boot 2环境,添加字段展平投影
|
||||
if (!SpringBootCompatibilityHelper.supportsNestedFieldReferences()) {
|
||||
try {
|
||||
// 根据lookup的目标集合添加相应的字段展平
|
||||
if ("users".equals(from)) {
|
||||
operations.add(Aggregation.project()
|
||||
.andInclude("_id", "userId", "productId", "createdAt")
|
||||
.and("user._id").as("userId")
|
||||
.and("user.username").as("username")
|
||||
.and("user.email").as("email"));
|
||||
} else if ("product".equals(from)) {
|
||||
operations.add(Aggregation.project()
|
||||
.andInclude("_id", "userId", "productId", "createdAt")
|
||||
.and("product._id").as("productId")
|
||||
.and("product.name").as("productName")
|
||||
.and("product.price").as("price"));
|
||||
} else if ("categories".equals(from)) {
|
||||
operations.add(Aggregation.project()
|
||||
.andInclude("_id", "userId", "productId", "createdAt")
|
||||
.and("category._id").as("categoryId")
|
||||
.and("category.name").as("categoryName"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("字段展平失败,继续使用原始字段: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容性统计操作
|
||||
*/
|
||||
public Map<String, Object> executeWithCompatibleStatistics() {
|
||||
Map<String, Object> statistics = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 如果是Spring Boot 2环境,使用展平字段策略
|
||||
if (!SpringBootCompatibilityHelper.supportsNestedFieldReferences()) {
|
||||
// 先添加字段展平操作
|
||||
List<AggregationOperation> compatibleOps = new ArrayList<>(operations);
|
||||
compatibleOps.addAll(SpringBootCompatibilityHelper.createCompatiblePipeline("shopping_record"));
|
||||
|
||||
// 1. 总数统计
|
||||
List<AggregationOperation> countOps = new ArrayList<>(compatibleOps);
|
||||
countOps.add(Aggregation.count().as("total"));
|
||||
Aggregation countAgg = Aggregation.newAggregation(countOps);
|
||||
AggregationResults<Map> countResults = mongoTemplate.aggregate(countAgg, sourceCollection, Map.class);
|
||||
if (!countResults.getMappedResults().isEmpty()) {
|
||||
statistics.put("count", countResults.getMappedResults().get(0));
|
||||
}
|
||||
|
||||
// 2. 使用展平字段的价格统计
|
||||
List<AggregationOperation> priceOps = new ArrayList<>(compatibleOps);
|
||||
priceOps.addAll(SpringBootCompatibilityHelper.createFlatFieldStatistics());
|
||||
Aggregation priceAgg = Aggregation.newAggregation(priceOps);
|
||||
AggregationResults<Map> priceResults = mongoTemplate.aggregate(priceAgg, sourceCollection, Map.class);
|
||||
if (!priceResults.getMappedResults().isEmpty()) {
|
||||
statistics.put("priceStats", priceResults.getMappedResults().get(0));
|
||||
}
|
||||
|
||||
// 3. 使用展平字段的分类统计
|
||||
List<AggregationOperation> categoryOps = new ArrayList<>(compatibleOps);
|
||||
categoryOps.addAll(SpringBootCompatibilityHelper.createFlatFieldCategoryStatistics());
|
||||
categoryOps.add(Aggregation.limit(10));
|
||||
Aggregation categoryAgg = Aggregation.newAggregation(categoryOps);
|
||||
AggregationResults<Map> categoryResults = mongoTemplate.aggregate(categoryAgg, sourceCollection, Map.class);
|
||||
statistics.put("categoryStats", categoryResults.getMappedResults());
|
||||
|
||||
} else {
|
||||
// Spring Boot 3环境,使用原始方法
|
||||
return executeWithSimpleStatistics();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("兼容性统计查询失败: " + e.getMessage());
|
||||
statistics.put("error", e.getMessage());
|
||||
}
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组操作支持 - 保留空数组的unwind
|
||||
*/
|
||||
public MongoAggregationBuilder unwindWithPreserveEmpty(String arrayField) {
|
||||
UnwindOperation unwind = Aggregation.unwind(arrayField, true);
|
||||
operations.add(unwind);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 采样支持 - 随机采样指定数量的文档
|
||||
*/
|
||||
public MongoAggregationBuilder sample(int size) {
|
||||
operations.add(Aggregation.sample(size));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据去重 - 基于指定字段
|
||||
*/
|
||||
public MongoAggregationBuilder distinct(String... fields) {
|
||||
GroupOperation groupOp = Aggregation.group(fields);
|
||||
operations.add(groupOp);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package org.example.mongosimple.prepareEntity;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果包装类
|
||||
* 用于封装MongoDB聚合查询的分页结果
|
||||
*
|
||||
* @param <T> 结果数据类型
|
||||
* @author admin
|
||||
*/
|
||||
public class PageResult<T> {
|
||||
private final List<T> content;
|
||||
private final long total;
|
||||
private final int page;
|
||||
private final int size;
|
||||
private final int totalPages;
|
||||
private final boolean hasNext;
|
||||
private final boolean hasPrevious;
|
||||
|
||||
/**
|
||||
* 构造分页结果
|
||||
*
|
||||
* @param content 当前页数据
|
||||
* @param total 总记录数
|
||||
* @param page 当前页码(从0开始)
|
||||
* @param size 每页大小
|
||||
*/
|
||||
public PageResult(List<T> content, long total, int page, int size) {
|
||||
this.content = content != null ? content : Collections.emptyList();
|
||||
this.total = total;
|
||||
this.page = page;
|
||||
this.size = size;
|
||||
this.totalPages = size > 0 ? (int) Math.ceil((double) total / size) : 0;
|
||||
this.hasNext = page < totalPages - 1;
|
||||
this.hasPrevious = page > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页数据
|
||||
*/
|
||||
public List<T> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总记录数
|
||||
*/
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页码(从0开始)
|
||||
*/
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每页大小
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总页数
|
||||
*/
|
||||
public int getTotalPages() {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有下一页
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有上一页
|
||||
*/
|
||||
public boolean hasPrevious() {
|
||||
return hasPrevious;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前页是否为空
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return content.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页元素数量
|
||||
*/
|
||||
public int getNumberOfElements() {
|
||||
return content.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为第一页
|
||||
*/
|
||||
public boolean isFirst() {
|
||||
return page == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为最后一页
|
||||
*/
|
||||
public boolean isLast() {
|
||||
return page >= totalPages - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下一页页码
|
||||
*/
|
||||
public int getNextPage() {
|
||||
return hasNext ? page + 1 : page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上一页页码
|
||||
*/
|
||||
public int getPreviousPage() {
|
||||
return hasPrevious ? page - 1 : page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分页信息摘要
|
||||
*/
|
||||
public String getSummary() {
|
||||
if (total == 0) {
|
||||
return "无数据";
|
||||
}
|
||||
|
||||
int start = page * size + 1;
|
||||
int end = Math.min((page + 1) * size, (int) total);
|
||||
|
||||
return String.format("第%d页,共%d页,显示第%d-%d条,共%d条记录",
|
||||
page + 1, totalPages, start, end, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PageResult{page=%d, size=%d, total=%d, totalPages=%d, elements=%d, hasNext=%s, hasPrevious=%s}",
|
||||
page, size, total, totalPages, content.size(), hasNext, hasPrevious);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
package org.example.mongosimple.prepareEntity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 搜索参数类 - 支持更多字段的动态查询
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ShoppingSearchParams {
|
||||
// 购物阶段
|
||||
private String phaseName;
|
||||
|
||||
// 用户相关
|
||||
private String usernamePattern;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String city;
|
||||
private List<String> cities;
|
||||
private Boolean isActive;
|
||||
private Integer minAge;
|
||||
private Integer maxAge;
|
||||
private LocalDateTime regStartDate;
|
||||
private LocalDateTime regEndDate;
|
||||
|
||||
// 产品相关
|
||||
private String productName;
|
||||
private String brand;
|
||||
private BigDecimal minPrice;
|
||||
private BigDecimal maxPrice;
|
||||
private Integer minStock;
|
||||
private Integer maxStock;
|
||||
private Boolean inStock;
|
||||
private String categoryName;
|
||||
private List<String> categoryNames;
|
||||
private List<String> productTags;
|
||||
|
||||
// 时间范围
|
||||
private LocalDateTime startDate;
|
||||
private LocalDateTime endDate;
|
||||
|
||||
// 排序和分页
|
||||
private String sortField = "product.price";
|
||||
private boolean ascending = false;
|
||||
private int offset = 0;
|
||||
private int limit = 20;
|
||||
|
||||
// 便捷构造方法
|
||||
public static ShoppingSearchParams builder() {
|
||||
return new ShoppingSearchParams();
|
||||
}
|
||||
|
||||
public ShoppingSearchParams phase(String phaseName) {
|
||||
this.phaseName = phaseName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams userLike(String usernamePattern) {
|
||||
this.usernamePattern = usernamePattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams email(String email) {
|
||||
this.email = email;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams city(String city) {
|
||||
this.city = city;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams cities(String... cities) {
|
||||
this.cities = List.of(cities);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams productLike(String productName) {
|
||||
this.productName = productName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams priceRange(BigDecimal min, BigDecimal max) {
|
||||
this.minPrice = min;
|
||||
this.maxPrice = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams brand(String brand) {
|
||||
this.brand = brand;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams categoryName(String categoryName) {
|
||||
this.categoryName = categoryName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams maxPrice(BigDecimal maxPrice) {
|
||||
this.maxPrice = maxPrice;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams categories(String... categories) {
|
||||
this.categoryNames = List.of(categories);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams inStock(Boolean inStock) {
|
||||
this.inStock = inStock;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams sort(String field, boolean asc) {
|
||||
this.sortField = field;
|
||||
this.ascending = asc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams page(int offset, int limit) {
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams sortField(String sortField) {
|
||||
this.sortField = sortField;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams ascending(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加分页相关的便捷方法
|
||||
public int getPage() {
|
||||
return limit > 0 ? offset / limit : 0;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public ShoppingSearchParams pageNumber(int page, int size) {
|
||||
this.offset = page * size;
|
||||
this.limit = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 手动添加getter方法以确保访问权限
|
||||
public String getSortField() {
|
||||
return sortField;
|
||||
}
|
||||
|
||||
public boolean isAscending() {
|
||||
return ascending;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
// 其他可能需要的getter方法
|
||||
public String getPhaseName() {
|
||||
return phaseName;
|
||||
}
|
||||
|
||||
public String getUsernamePattern() {
|
||||
return usernamePattern;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public List<String> getCities() {
|
||||
return cities;
|
||||
}
|
||||
|
||||
public Boolean getIsActive() {
|
||||
return isActive;
|
||||
}
|
||||
|
||||
public Integer getMinAge() {
|
||||
return minAge;
|
||||
}
|
||||
|
||||
public Integer getMaxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
public LocalDateTime getRegStartDate() {
|
||||
return regStartDate;
|
||||
}
|
||||
|
||||
public LocalDateTime getRegEndDate() {
|
||||
return regEndDate;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public String getBrand() {
|
||||
return brand;
|
||||
}
|
||||
|
||||
public BigDecimal getMinPrice() {
|
||||
return minPrice;
|
||||
}
|
||||
|
||||
public BigDecimal getMaxPrice() {
|
||||
return maxPrice;
|
||||
}
|
||||
|
||||
public Integer getMinStock() {
|
||||
return minStock;
|
||||
}
|
||||
|
||||
public Integer getMaxStock() {
|
||||
return maxStock;
|
||||
}
|
||||
|
||||
public Boolean getInStock() {
|
||||
return inStock;
|
||||
}
|
||||
|
||||
public String getCategoryName() {
|
||||
return categoryName;
|
||||
}
|
||||
|
||||
public List<String> getCategoryNames() {
|
||||
return categoryNames;
|
||||
}
|
||||
|
||||
public List<String> getProductTags() {
|
||||
return productTags;
|
||||
}
|
||||
|
||||
public LocalDateTime getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public LocalDateTime getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,400 @@
|
|||
package org.example.mongosimple.prepareEntity;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring Boot 2/3兼容性助手
|
||||
* 解决不同版本间的API差异
|
||||
*
|
||||
* @author admin
|
||||
*/
|
||||
public class SpringBootCompatibilityHelper {
|
||||
|
||||
/**
|
||||
* 创建兼容的价格统计group操作
|
||||
* Spring Boot 2需要明确的字段路径
|
||||
*/
|
||||
public static List<AggregationOperation> createPriceStatisticsOperations() {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 方案1: 尝试使用完整的嵌套字段路径
|
||||
GroupOperation groupOp = Aggregation.group()
|
||||
.sum("product.price").as("totalAmount")
|
||||
.avg("product.price").as("avgPrice")
|
||||
.min("product.price").as("minPrice")
|
||||
.max("product.price").as("maxPrice")
|
||||
.count().as("orderCount");
|
||||
operations.add(groupOp);
|
||||
} catch (Exception e) {
|
||||
System.err.println("嵌套字段引用失败,尝试投影后的字段: " + e.getMessage());
|
||||
|
||||
try {
|
||||
// 方案2: 先添加投影操作,将嵌套字段提升到顶层
|
||||
operations.add(Aggregation.project()
|
||||
.and("product.price").as("price")
|
||||
.and("product.name").as("productName")
|
||||
.and("product._id").as("productId")
|
||||
.andInclude("_id", "userId", "createdAt"));
|
||||
|
||||
// 然后使用顶层字段进行group操作
|
||||
GroupOperation groupOp = Aggregation.group()
|
||||
.sum("price").as("totalAmount")
|
||||
.avg("price").as("avgPrice")
|
||||
.min("price").as("minPrice")
|
||||
.max("price").as("maxPrice")
|
||||
.count().as("orderCount");
|
||||
operations.add(groupOp);
|
||||
} catch (Exception e2) {
|
||||
System.err.println("投影方案也失败,使用最简单的统计: " + e2.getMessage());
|
||||
|
||||
// 方案3: 最简单的统计,只计算数量
|
||||
operations.add(Aggregation.group().count().as("orderCount"));
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建兼容的分类统计group操作
|
||||
*/
|
||||
public static List<AggregationOperation> createCategoryStatisticsOperations() {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 尝试Spring Boot 3风格
|
||||
GroupOperation groupOp = Aggregation.group("category.name")
|
||||
.count().as("count")
|
||||
.sum("product.price").as("totalAmount")
|
||||
.avg("product.price").as("avgPrice");
|
||||
operations.add(groupOp);
|
||||
operations.add(Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "count"));
|
||||
} catch (Exception e) {
|
||||
// 降级到Spring Boot 2风格
|
||||
try {
|
||||
GroupOperation groupOp = Aggregation.group("categoryName")
|
||||
.count().as("count")
|
||||
.sum("price").as("totalAmount");
|
||||
operations.add(groupOp);
|
||||
operations.add(Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "count"));
|
||||
} catch (Exception e2) {
|
||||
// 最简单的分组
|
||||
operations.add(Aggregation.group("_id").count().as("count"));
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建兼容的用户统计group操作
|
||||
*/
|
||||
public static List<AggregationOperation> createUserStatisticsOperations() {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 尝试Spring Boot 3风格
|
||||
GroupOperation groupOp = Aggregation.group("user._id")
|
||||
.first("user.username").as("username")
|
||||
.first("address.city").as("city")
|
||||
.count().as("totalOrders")
|
||||
.sum("product.price").as("totalSpent")
|
||||
.avg("product.price").as("avgOrderValue");
|
||||
operations.add(groupOp);
|
||||
} catch (Exception e) {
|
||||
// 降级到Spring Boot 2风格
|
||||
try {
|
||||
GroupOperation groupOp = Aggregation.group("userId")
|
||||
.first("username").as("username")
|
||||
.first("city").as("city")
|
||||
.count().as("totalOrders")
|
||||
.sum("price").as("totalSpent");
|
||||
operations.add(groupOp);
|
||||
} catch (Exception e2) {
|
||||
// 最简单的用户分组
|
||||
operations.add(Aggregation.group("userId").count().as("totalOrders"));
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建兼容的产品统计group操作
|
||||
* 使用明确的字段投影策略
|
||||
*/
|
||||
public static List<AggregationOperation> createProductStatisticsOperations() {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 方案1: 尝试直接使用嵌套字段
|
||||
GroupOperation groupOp = Aggregation.group("product._id")
|
||||
.first("product.name").as("productName")
|
||||
.first("product.price").as("price")
|
||||
.first("category.name").as("categoryName")
|
||||
.count().as("purchaseCount")
|
||||
.sum("product.price").as("totalRevenue");
|
||||
operations.add(groupOp);
|
||||
operations.add(Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "purchaseCount"));
|
||||
} catch (Exception e) {
|
||||
System.err.println("产品嵌套字段分组失败,使用投影方案: " + e.getMessage());
|
||||
|
||||
try {
|
||||
// 方案2: 先投影,再分组
|
||||
operations.add(Aggregation.project()
|
||||
.and("product._id").as("productId")
|
||||
.and("product.name").as("productName")
|
||||
.and("product.price").as("price")
|
||||
.and("category.name").as("categoryName")
|
||||
.andInclude("_id", "userId", "createdAt"));
|
||||
|
||||
GroupOperation groupOp = Aggregation.group("productId")
|
||||
.first("productName").as("productName")
|
||||
.first("price").as("price")
|
||||
.first("categoryName").as("categoryName")
|
||||
.count().as("purchaseCount")
|
||||
.sum("price").as("totalRevenue");
|
||||
operations.add(groupOp);
|
||||
operations.add(Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "purchaseCount"));
|
||||
} catch (Exception e2) {
|
||||
System.err.println("投影分组也失败,使用原始字段: " + e2.getMessage());
|
||||
|
||||
try {
|
||||
// 方案3: 使用原始字段名
|
||||
GroupOperation groupOp = Aggregation.group("productId")
|
||||
.count().as("purchaseCount");
|
||||
operations.add(groupOp);
|
||||
operations.add(Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "purchaseCount"));
|
||||
} catch (Exception e3) {
|
||||
// 方案4: 最简单的分组
|
||||
operations.add(Aggregation.group("_id").count().as("purchaseCount"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建兼容的投影操作
|
||||
*/
|
||||
public static ProjectionOperation createCompatibleProjection() {
|
||||
try {
|
||||
// 尝试Spring Boot 3风格
|
||||
return Aggregation.project()
|
||||
.and("user.username").as("username")
|
||||
.and("product.name").as("productName")
|
||||
.and("product.price").as("price")
|
||||
.and("category.name").as("categoryName")
|
||||
.and("address.city").as("city")
|
||||
.and("phase.name").as("phaseName");
|
||||
} catch (Exception e) {
|
||||
// 降级到Spring Boot 2风格
|
||||
try {
|
||||
return Aggregation.project()
|
||||
.and("username").as("username")
|
||||
.and("productName").as("productName")
|
||||
.and("price").as("price")
|
||||
.and("categoryName").as("categoryName")
|
||||
.and("city").as("city")
|
||||
.and("phaseName").as("phaseName");
|
||||
} catch (Exception e2) {
|
||||
// 最简单的投影
|
||||
return Aggregation.project()
|
||||
.andInclude("_id", "username", "productName", "price");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测Spring Boot版本并返回适当的字段引用
|
||||
*/
|
||||
public static String getCompatibleFieldReference(String nestedField, String fallbackField) {
|
||||
try {
|
||||
// 尝试使用嵌套字段引用
|
||||
// 这里可以添加实际的版本检测逻辑
|
||||
return nestedField;
|
||||
} catch (Exception e) {
|
||||
return fallbackField;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建兼容的排序操作
|
||||
*/
|
||||
public static AggregationOperation createCompatibleSort(String field, boolean ascending) {
|
||||
try {
|
||||
return Aggregation.sort(
|
||||
ascending ?
|
||||
org.springframework.data.domain.Sort.Direction.ASC :
|
||||
org.springframework.data.domain.Sort.Direction.DESC,
|
||||
field
|
||||
);
|
||||
} catch (Exception e) {
|
||||
// 降级方案
|
||||
return Aggregation.sort(
|
||||
org.springframework.data.domain.Sort.Direction.DESC,
|
||||
"_id"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Spring Data MongoDB版本信息
|
||||
*/
|
||||
public static String getSpringDataMongoVersion() {
|
||||
try {
|
||||
Package pkg = Aggregation.class.getPackage();
|
||||
return pkg.getImplementationVersion() != null ?
|
||||
pkg.getImplementationVersion() : "Unknown";
|
||||
} catch (Exception e) {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否支持复杂的字段引用
|
||||
*/
|
||||
public static boolean supportsNestedFieldReferences() {
|
||||
try {
|
||||
// 尝试创建一个包含嵌套字段引用的操作
|
||||
Aggregation.group().sum("nested.field").as("test");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Spring Boot 2兼容的聚合管道
|
||||
* 核心策略:先投影展平字段,再进行复杂操作
|
||||
*/
|
||||
public static List<AggregationOperation> createCompatiblePipeline(String targetCollection) {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 策略1: 创建标准化的投影,将所有嵌套字段展平
|
||||
ProjectionOperation projection = Aggregation.project()
|
||||
// 用户相关字段
|
||||
.and("user._id").as("userId")
|
||||
.and("user.username").as("username")
|
||||
.and("user.email").as("email")
|
||||
|
||||
// 产品相关字段
|
||||
.and("product._id").as("productId")
|
||||
.and("product.name").as("productName")
|
||||
.and("product.price").as("price")
|
||||
.and("product.brand").as("brand")
|
||||
|
||||
// 分类相关字段
|
||||
.and("category._id").as("categoryId")
|
||||
.and("category.name").as("categoryName")
|
||||
|
||||
// 地址相关字段
|
||||
.and("address._id").as("addressId")
|
||||
.and("address.city").as("city")
|
||||
.and("address.province").as("province")
|
||||
|
||||
// 阶段相关字段
|
||||
.and("phase._id").as("phaseId")
|
||||
.and("phase.name").as("phaseName")
|
||||
|
||||
// 保留原始字段
|
||||
.andInclude("_id", "createdAt", "updatedAt");
|
||||
|
||||
operations.add(projection);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("创建标准化投影失败: " + e.getMessage());
|
||||
|
||||
// 降级方案:只保留基本字段
|
||||
try {
|
||||
ProjectionOperation simpleProjection = Aggregation.project()
|
||||
.andInclude("_id", "userId", "productId", "createdAt")
|
||||
.and("user").as("userInfo")
|
||||
.and("product").as("productInfo");
|
||||
operations.add(simpleProjection);
|
||||
} catch (Exception e2) {
|
||||
System.err.println("简单投影也失败,跳过投影步骤: " + e2.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基于展平字段的统计操作
|
||||
*/
|
||||
public static List<AggregationOperation> createFlatFieldStatistics() {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 使用展平后的字段进行统计
|
||||
GroupOperation groupOp = Aggregation.group()
|
||||
.sum("price").as("totalAmount")
|
||||
.avg("price").as("avgPrice")
|
||||
.min("price").as("minPrice")
|
||||
.max("price").as("maxPrice")
|
||||
.count().as("orderCount");
|
||||
operations.add(groupOp);
|
||||
} catch (Exception e) {
|
||||
System.err.println("展平字段统计失败: " + e.getMessage());
|
||||
operations.add(Aggregation.group().count().as("orderCount"));
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基于展平字段的分类统计
|
||||
*/
|
||||
public static List<AggregationOperation> createFlatFieldCategoryStatistics() {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 使用展平后的字段进行分类统计
|
||||
GroupOperation groupOp = Aggregation.group("categoryName")
|
||||
.count().as("count")
|
||||
.sum("price").as("totalAmount")
|
||||
.avg("price").as("avgPrice");
|
||||
operations.add(groupOp);
|
||||
operations.add(Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "count"));
|
||||
} catch (Exception e) {
|
||||
System.err.println("展平字段分类统计失败: " + e.getMessage());
|
||||
operations.add(Aggregation.group("categoryId").count().as("count"));
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基于展平字段的产品统计
|
||||
*/
|
||||
public static List<AggregationOperation> createFlatFieldProductStatistics() {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 使用展平后的字段进行产品统计
|
||||
GroupOperation groupOp = Aggregation.group("productId")
|
||||
.first("productName").as("productName")
|
||||
.first("price").as("price")
|
||||
.first("categoryName").as("categoryName")
|
||||
.count().as("purchaseCount")
|
||||
.sum("price").as("totalRevenue");
|
||||
operations.add(groupOp);
|
||||
operations.add(Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "purchaseCount"));
|
||||
} catch (Exception e) {
|
||||
System.err.println("展平字段产品统计失败: " + e.getMessage());
|
||||
operations.add(Aggregation.group("productId").count().as("purchaseCount"));
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package org.example.mongosimple.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.example.mongosimple.entity.ShoppingResult;
|
||||
import org.example.mongosimple.prepareEntity.FieldMapping;
|
||||
import org.example.mongosimple.prepareEntity.MongoAggregationBuilder;
|
||||
import org.example.mongosimple.prepareEntity.PageResult;
|
||||
import org.example.mongosimple.prepareEntity.ShoppingSearchParams;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ShoppingAggregationService {
|
||||
|
||||
private final MongoTemplate mongoTemplate;
|
||||
|
||||
private FieldMapping createShoppingFieldMapping() {
|
||||
return new FieldMapping()
|
||||
.setDefaultIncludeFields("_id", "createdAt")
|
||||
|
||||
// 添加常用的投影字段映射
|
||||
.addProjectionMapping("user.username", "username")
|
||||
.addProjectionMapping("user.email", "email")
|
||||
.addProjectionMapping("user.phone", "phone")
|
||||
.addProjectionMapping("user.age", "age")
|
||||
.addProjectionMapping("user.isActive", "isActive")
|
||||
.addProjectionMapping("product.name", "productName")
|
||||
.addProjectionMapping("product.price", "price")
|
||||
.addProjectionMapping("product.brand", "brand")
|
||||
.addProjectionMapping("product.stock", "stock")
|
||||
.addProjectionMapping("category.name", "categoryName")
|
||||
.addProjectionMapping("address.city", "city")
|
||||
.addProjectionMapping("phase.name", "phaseName")
|
||||
|
||||
// 添加排序字段映射
|
||||
.addSortMapping("product.price", "price");
|
||||
}
|
||||
|
||||
|
||||
public PageResult<ShoppingResult> searchWithCriteriaAndPagination(ShoppingSearchParams params) {
|
||||
try {
|
||||
// 创建字段映射配置
|
||||
FieldMapping fieldMapping = new FieldMapping()
|
||||
.setDefaultIncludeFields("_id", "createdAt")
|
||||
// 添加常用的投影字段映射
|
||||
.addProjectionMapping("user.username", "username")
|
||||
.addProjectionMapping("user.email", "email")
|
||||
.addProjectionMapping("user.phone", "phone")
|
||||
.addProjectionMapping("user.age", "age")
|
||||
.addProjectionMapping("user.isActive", "isActive")
|
||||
.addProjectionMapping("product.name", "productName")
|
||||
.addProjectionMapping("product.price", "price")
|
||||
.addProjectionMapping("product.brand", "brand")
|
||||
.addProjectionMapping("product.stock", "stock")
|
||||
.addProjectionMapping("category.name", "categoryName")
|
||||
.addProjectionMapping("address.city", "city")
|
||||
.addProjectionMapping("phase.name", "phaseName")
|
||||
|
||||
// 添加排序字段映射
|
||||
.addSortMapping("product.price", "price");
|
||||
|
||||
// 获取兼容的排序字段
|
||||
String compatibleSortField = fieldMapping.getCompatibleSortField(params.getSortField(), "price");
|
||||
|
||||
return MongoAggregationBuilder.fromWithCompatibility(mongoTemplate, "shopping_record")
|
||||
// 关联所有需要的表
|
||||
.lookupAndUnwind("shopping_phase", "shoppingPhaseId", "_id", "phase")
|
||||
.lookupAndUnwind("users", "userId", "_id", "user")
|
||||
.lookupAndUnwind("address", "user.addressId", "_id", "address")
|
||||
.lookupAndUnwind("product", "productId", "_id", "product")
|
||||
.lookupAndUnwind("categories", "product.categoryId", "_id", "category")
|
||||
|
||||
// 使用动态Criteria构建器
|
||||
.matchDynamic(criteria -> criteria
|
||||
.whenNotEmpty(params.getPhaseName(), "phase.name")
|
||||
.whenNotEmptyLike(params.getUsernamePattern(), "user.username")
|
||||
.whenNotEmpty(params.getEmail(), "user.email")
|
||||
.whenNotEmpty(params.getCity(), "address.city")
|
||||
.whenNotEmptyIn(params.getCities(), "address.city")
|
||||
.whenNotEmptyLike(params.getProductName(), "product.name")
|
||||
.whenNotEmpty(params.getBrand(), "product.brand")
|
||||
.whenRange(params.getMinPrice(), params.getMaxPrice(), "product.price")
|
||||
.whenRange(params.getMinStock(), params.getMaxStock(), "product.stock")
|
||||
.whenNotNull(params.getInStock(), "product.inStock")
|
||||
.whenNotEmpty(params.getCategoryName(), "category.name")
|
||||
.whenNotEmptyIn(params.getCategoryNames(), "category.name")
|
||||
.whenNotEmptyIn(params.getProductTags(), "product.tags")
|
||||
.whenDateRange(params.getStartDate(), params.getEndDate(), "createdAt")
|
||||
.whenNotNull(params.getIsActive(), "user.isActive")
|
||||
.whenRange(params.getMinAge(), params.getMaxAge(), "user.age")
|
||||
.whenDateRange(params.getRegStartDate(), params.getRegEndDate(), "user.registrationDate")
|
||||
)
|
||||
|
||||
// 使用手动配置的投影映射
|
||||
.project(fieldMapping.createProjectionFunction())
|
||||
|
||||
// 排序 - 使用配置获取的兼容字段名
|
||||
.sort(compatibleSortField, params.isAscending())
|
||||
|
||||
// 使用facet分页 - 一次查询获取总数和分页数据
|
||||
.executeWithPagination(ShoppingResult.class, params.getPage(), params.getSize());
|
||||
} catch (Exception e) {
|
||||
System.err.println("Facet分页查询失败,使用传统分页: " + e.getMessage());
|
||||
System.err.println("错误详情: " + e.getClass().getSimpleName() + " - " + e.getMessage());
|
||||
// 降级到传统分页
|
||||
// return searchWithTraditionalPagination(params);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* *//**
|
||||
* 传统分页查询 - 降级方案
|
||||
*//*
|
||||
private PageResult<ShoppingResult> searchWithTraditionalPagination(ShoppingSearchParams params) {
|
||||
try {
|
||||
// 1. 获取总数
|
||||
List<ShoppingResult> allResults = searchWithCriteria(params);
|
||||
long total = allResults.size();
|
||||
|
||||
// 2. 获取分页数据
|
||||
int offset = params.getPage() * params.getSize();
|
||||
int limit = params.getSize();
|
||||
|
||||
List<ShoppingResult> pagedResults;
|
||||
if (offset >= allResults.size()) {
|
||||
pagedResults = new ArrayList<>();
|
||||
} else {
|
||||
int endIndex = Math.min(offset + limit, allResults.size());
|
||||
pagedResults = allResults.subList(offset, endIndex);
|
||||
}
|
||||
|
||||
return new PageResult<>(pagedResults, total, params.getPage(), params.getSize());
|
||||
} catch (Exception e) {
|
||||
System.err.println("传统分页也失败: " + e.getMessage());
|
||||
return new PageResult<>(new ArrayList<>(), 0, params.getPage(), params.getSize());
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
spring:
|
||||
data:
|
||||
mongodb:
|
||||
uri: mongodb://localhost:27017/test # 数据库名 test
|
|
@ -0,0 +1,13 @@
|
|||
package org.example.mongosimple;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class MongoSimpleApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue