From 8e2d8e45e827e50534e2329e18e32f132cf6e239 Mon Sep 17 00:00:00 2001 From: bamanker <27054792@qq.com> Date: Fri, 12 Dec 2025 17:09:08 +0800 Subject: [PATCH] =?UTF-8?q?assert=20test=20=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 2 + .gitignore | 33 ++ .mvn/wrapper/maven-wrapper.properties | 3 + mvnw | 295 ++++++++++++++++++ mvnw.cmd | 189 +++++++++++ pom.xml | 151 +++++++++ .../SpMybatisPlusApplication.java | 17 + .../spmybatisplus/config/JacksonConfig.java | 31 ++ .../config/MybatisPlusConfig.java | 20 ++ .../spmybatisplus/config/WebMvcConfig.java | 26 ++ .../controller/UserController.java | 142 +++++++++ .../bamanker/spmybatisplus/entity/Order.java | 21 ++ .../spmybatisplus/entity/OrderVO.java | 18 ++ .../bamanker/spmybatisplus/entity/User.java | 37 +++ .../entity/dto/ErrorResponse.java | 23 ++ .../entity/dto/UserCreateRequest.java | 39 +++ .../spmybatisplus/entity/pojo/Result.java | 45 +++ .../spmybatisplus/enums/GenderEnum.java | 20 ++ .../exception/BusinessException.java | 42 +++ .../exception/GlobalExceptionHandler.java | 141 +++++++++ .../spmybatisplus/mapper/UserMapper.java | 18 ++ .../spmybatisplus/service/UserService.java | 9 + .../service/impl/UserServiceImpl.java | 25 ++ src/main/resources/application.yml | 96 ++++++ src/main/resources/db/init.sql | 35 +++ src/main/resources/mapper/UserMapper.xml | 20 ++ src/main/resources/spy.properties | 24 ++ .../SpMybatisPlusApplicationTests.java | 134 ++++++++ .../spmybatisplus/UserControllerTest.java | 104 ++++++ 29 files changed, 1760 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/java/com/bamanker/spmybatisplus/SpMybatisPlusApplication.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/config/JacksonConfig.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/config/MybatisPlusConfig.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/config/WebMvcConfig.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/controller/UserController.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/entity/Order.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/entity/OrderVO.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/entity/User.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/entity/dto/ErrorResponse.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/entity/dto/UserCreateRequest.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/entity/pojo/Result.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/enums/GenderEnum.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/exception/BusinessException.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/mapper/UserMapper.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/service/UserService.java create mode 100644 src/main/java/com/bamanker/spmybatisplus/service/impl/UserServiceImpl.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/db/init.sql create mode 100644 src/main/resources/mapper/UserMapper.xml create mode 100644 src/main/resources/spy.properties create mode 100644 src/test/java/com/bamanker/spmybatisplus/SpMybatisPlusApplicationTests.java create mode 100644 src/test/java/com/bamanker/spmybatisplus/UserControllerTest.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..667aaef --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c0bcafe --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -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 "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +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" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d2c3c44 --- /dev/null +++ b/pom.xml @@ -0,0 +1,151 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.0 + + + com.bamanker + sp-mybatis-plus + 0.0.1-SNAPSHOT + sp-mybatis-plus + sp-mybatis-plus + + + + + + + + + + + + + + + 25 + + + + org.springframework.boot + spring-boot-h2console + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-webmvc-test + + + com.baomidou + mybatis-plus-spring-boot4-starter + 3.5.15 + + + + com.baomidou + mybatis-plus-jsqlparser + 3.5.15 + + + p6spy + p6spy + 3.9.1 + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + 4.5.0 + + + org.apache.commons + commons-lang3 + 3.19.0 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-validation + + + + com.dtflys.forest + forest-spring-boot3-starter + 1.5.36 + + + cn.hutool + hutool-all + 5.8.40 + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.springframework.boot + spring-boot-configuration-processor + + + org.projectlombok + lombok + + + + + + org.graalvm.buildtools + native-maven-plugin + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/bamanker/spmybatisplus/SpMybatisPlusApplication.java b/src/main/java/com/bamanker/spmybatisplus/SpMybatisPlusApplication.java new file mode 100644 index 0000000..f6ed15f --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/SpMybatisPlusApplication.java @@ -0,0 +1,17 @@ +package com.bamanker.spmybatisplus; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@MapperScan(basePackages = "com.bamanker.spmybatisplus.mapper") +@EnableScheduling +public class SpMybatisPlusApplication { + + public static void main(String[] args) { + SpringApplication.run(SpMybatisPlusApplication.class, args); + } + +} diff --git a/src/main/java/com/bamanker/spmybatisplus/config/JacksonConfig.java b/src/main/java/com/bamanker/spmybatisplus/config/JacksonConfig.java new file mode 100644 index 0000000..a669057 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/config/JacksonConfig.java @@ -0,0 +1,31 @@ +package com.bamanker.spmybatisplus.config; + + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverters; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; + + +import java.text.SimpleDateFormat; + +/** + * @descriptions 消息转换器配置 + * @author bamanker + * @date 2025/12/12 16:08 + */ +@Configuration +public class JacksonConfig implements WebMvcConfigurer { + + @Override + public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) { + JsonMapper jsonMapper = JsonMapper.builder() + .findAndAddModules() + .enable(SerializationFeature.INDENT_OUTPUT) + .defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")) + .build(); + builder.withJsonConverter(new JacksonJsonHttpMessageConverter(jsonMapper)); + } +} diff --git a/src/main/java/com/bamanker/spmybatisplus/config/MybatisPlusConfig.java b/src/main/java/com/bamanker/spmybatisplus/config/MybatisPlusConfig.java new file mode 100644 index 0000000..0fcb4a4 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/config/MybatisPlusConfig.java @@ -0,0 +1,20 @@ +package com.bamanker.spmybatisplus.config; + +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@MapperScan("com.bamanker.spmybatisplus.mapper") +public class MybatisPlusConfig { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); + return interceptor; + } + +} diff --git a/src/main/java/com/bamanker/spmybatisplus/config/WebMvcConfig.java b/src/main/java/com/bamanker/spmybatisplus/config/WebMvcConfig.java new file mode 100644 index 0000000..5ce08e6 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/config/WebMvcConfig.java @@ -0,0 +1,26 @@ +package com.bamanker.spmybatisplus.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author bamanker + * @descriptions Spring MVC自定义配置类 + * * 实现WebMvcConfigurer接口,可以自定义各种MVC相关配置 + * * 注意: 不要加@EnableWebMvc注解,否则Spring Boot的自动配置就失效了 + * @date 2025/12/11 23:21 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") // 允许跨域的路径 + .allowedOrigins("http://localhost:3000", "http://localhost:8080") // 允许的源 + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的HTTP方法 + .allowedHeaders("*") // 允许所有请求头 + .allowCredentials(true) // 允许携带凭证 + .maxAge(3600); // 预检请求缓存时间 + } +} diff --git a/src/main/java/com/bamanker/spmybatisplus/controller/UserController.java b/src/main/java/com/bamanker/spmybatisplus/controller/UserController.java new file mode 100644 index 0000000..ebb6f41 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/controller/UserController.java @@ -0,0 +1,142 @@ +package com.bamanker.spmybatisplus.controller; + +import com.bamanker.spmybatisplus.entity.User; +import com.bamanker.spmybatisplus.entity.dto.UserCreateRequest; +import com.bamanker.spmybatisplus.entity.pojo.Result; +import com.bamanker.spmybatisplus.service.UserService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static net.sf.jsqlparser.util.validation.metadata.NamedObject.user; + +/** + * @param + * @author bamanker + * @descriptions 用户控制器 @RestController = @Controller + @ResponseBody + * 所有方法返回的数据都会自动转成JSON + * @date 2025/12/11 22:48 + * @return + */ +@RestController +@RequestMapping("/api/users") // 定义基础路径,所有接口都是/api/users开 +public class UserController { + // 构造器注入 + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + /** + * GET请求: 获取欢迎信息 + * 访问地址: http://localhost:8080/api/users/hello + */ + @GetMapping("/hello") + public String hello() { + return "Hello Spring Boot 4 MVC!"; + } + + /** + * @param + * @return com.bamanker.spmybatisplus.entity.pojo.Result> + * @descriptions 获取所有用户列表 + * @author bamanker + * @date 2025/12/11 23:02 + */ + @GetMapping() + public Result> getAll() { + List users = userService.list(); + if (users != null) { + return Result.success(users); + } + return Result.error(users); + } + + /** + * @param id + * @return org.springframework.http.ResponseEntity> + * @descriptions 根据id获取用户 + * @author bamanker + * @date 2025/12/11 23:02 + */ + @GetMapping("/{id}") + public ResponseEntity> findById(@PathVariable Long id) { + User user = userService.getById(id); + if (user == null) { + return ResponseEntity.notFound().build(); + } else { + return ResponseEntity.ok(Result.success(user)); + } + } + + @PostMapping + public ResponseEntity> createUser(@RequestBody UserCreateRequest userRequest) { + User user = User.builder().userNo(userRequest.getUserNo()) + .nickname(userRequest.getNickname()) + .gender(userRequest.getGender()) + .phone(userRequest.getPhone()) + .email(userRequest.getEmail()) + .birthday(userRequest.getBirthday()) + .build(); + boolean isSuccess = userService.save(user); + if (isSuccess) { + //201 + return ResponseEntity.status(HttpStatus.CREATED).body(Result.success(user)); + } + return ResponseEntity.internalServerError().body(Result.error(user)); + + } + + /** + * @param id + * @param userRequest + * @return org.springframework.http.ResponseEntity> + * @descriptions 更新用户信息 + * @author bamanker + * @date 2025/12/11 23:02 + */ + @PutMapping("/{id}") + public ResponseEntity> updateUser( + @PathVariable Long id, + @RequestBody UserCreateRequest userRequest) { + + User user = userService.getById(id); + if (user == null) { + return ResponseEntity.notFound().build(); + } + user.setUserNo(userRequest.getUserNo()); + user.setNickname(userRequest.getNickname()); + user.setGender(userRequest.getGender()); + user.setPhone(userRequest.getPhone()); + user.setEmail(userRequest.getEmail()); + user.setBirthday(userRequest.getBirthday()); + + boolean isSuccess = userService.updateById(user); + if (isSuccess) { + return ResponseEntity.ok(Result.success(user)); + } + return ResponseEntity.internalServerError().body(Result.error(user)); + + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteUser(@PathVariable Long id) { + + boolean isSuccess = userService.removeById(id); + if (isSuccess) { + // 204 No Content + return ResponseEntity.noContent().build(); + } + // 404 Not Found + return ResponseEntity.notFound().build(); + + + } + +} + + diff --git a/src/main/java/com/bamanker/spmybatisplus/entity/Order.java b/src/main/java/com/bamanker/spmybatisplus/entity/Order.java new file mode 100644 index 0000000..b4e4528 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/entity/Order.java @@ -0,0 +1,21 @@ +package com.bamanker.spmybatisplus.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Builder +@TableName(value = "tb_order") +public class Order { + @TableId(type = IdType.AUTO) + private Long id; + private Long userId; + private Long orderId; + private String goodsName; + private BigDecimal goodsPrice; +} diff --git a/src/main/java/com/bamanker/spmybatisplus/entity/OrderVO.java b/src/main/java/com/bamanker/spmybatisplus/entity/OrderVO.java new file mode 100644 index 0000000..bed38b2 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/entity/OrderVO.java @@ -0,0 +1,18 @@ +package com.bamanker.spmybatisplus.entity; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class OrderVO { + + private Long orderId; + private Long userId; + private String goodsName; + private BigDecimal goodsPrice; + private String nickName; + private Integer gender; + private String phone; + +} diff --git a/src/main/java/com/bamanker/spmybatisplus/entity/User.java b/src/main/java/com/bamanker/spmybatisplus/entity/User.java new file mode 100644 index 0000000..dc05ec9 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/entity/User.java @@ -0,0 +1,37 @@ +package com.bamanker.spmybatisplus.entity; + +import com.bamanker.spmybatisplus.enums.GenderEnum; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * @descriptions 用户实体类DTO(数据传输对象) + * @author bamanker + * @date 2025/12/11 23:06 + */ +@Data +@Builder +@TableName(value = "tb_user") +public class User { + @TableId(type = IdType.AUTO) + private Long id; + private String userNo; + private String nickname; + private String email; + private String phone; + private GenderEnum gender; + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date birthday; + @TableLogic + private Integer isDelete; + private Date createTime; + private Date updateTime; +} diff --git a/src/main/java/com/bamanker/spmybatisplus/entity/dto/ErrorResponse.java b/src/main/java/com/bamanker/spmybatisplus/entity/dto/ErrorResponse.java new file mode 100644 index 0000000..5889cd0 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/entity/dto/ErrorResponse.java @@ -0,0 +1,23 @@ +package com.bamanker.spmybatisplus.entity.dto; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * @author bamanker + * @descriptions 错误响应DTO + * @date 2025/12/11 23:26 + */ +@Data +public class ErrorResponse { + private Integer code; + private String message; + private Map errors; + private LocalDateTime timestamp; + + public ErrorResponse() { + timestamp = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/bamanker/spmybatisplus/entity/dto/UserCreateRequest.java b/src/main/java/com/bamanker/spmybatisplus/entity/dto/UserCreateRequest.java new file mode 100644 index 0000000..210c8fe --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/entity/dto/UserCreateRequest.java @@ -0,0 +1,39 @@ +package com.bamanker.spmybatisplus.entity.dto; + +import com.bamanker.spmybatisplus.enums.GenderEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * @author bamanker + * @descriptions 创建用户请求DTO + * @date 2025/12/11 23:09 + */ +@Data +public class UserCreateRequest { + + @NotBlank(message = "用户编号不能为空") + private String userNo; + @NotBlank(message = "用户昵称不能为空") + private String nickname; + @NotBlank(message = "用户邮箱不能为空") + @Email(message = "邮箱格式错误") + private String email; + @NotBlank(message = "用户手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{8}$", message = "手机号格式错误") + private String phone; + @NotBlank(message = "用户性别不能为空") + @Pattern(regexp = "^[0-1]$", message = "性别格式错误") + private GenderEnum gender; + @NotBlank(message = "用户生日不能为空") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date birthday; + +} diff --git a/src/main/java/com/bamanker/spmybatisplus/entity/pojo/Result.java b/src/main/java/com/bamanker/spmybatisplus/entity/pojo/Result.java new file mode 100644 index 0000000..ecd826b --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/entity/pojo/Result.java @@ -0,0 +1,45 @@ +package com.bamanker.spmybatisplus.entity.pojo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @param + * @author bamanker + * @descriptions 统一的结果返回类型 + * @date 2025/12/11 15:33 + * @return + */ +@Data +public class Result implements Serializable { + + private T data; + private Integer code; + private String message; + + public static Result success(T data) { + Result result = new Result<>(); + result.setData(data); + result.setCode(1); + result.setMessage("success"); + return result; + } + + public static Result success() { + return success(null); + } + + public static Result error(T data) { + Result result = new Result<>(); + result.setData(data); + result.setCode(-1); + result.setMessage("error"); + return result; + } + + public static Result error() { + return error(null); + } + +} diff --git a/src/main/java/com/bamanker/spmybatisplus/enums/GenderEnum.java b/src/main/java/com/bamanker/spmybatisplus/enums/GenderEnum.java new file mode 100644 index 0000000..6f22d73 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/enums/GenderEnum.java @@ -0,0 +1,20 @@ +package com.bamanker.spmybatisplus.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import lombok.Getter; + +@Getter +public enum GenderEnum { + MAN(0, "男"), + WOMEN(1, "女"), + ; + + @EnumValue + private final Integer code; + private final String desc; + + GenderEnum(Integer code, String desc) { + this.code = code; + this.desc = desc; + } +} diff --git a/src/main/java/com/bamanker/spmybatisplus/exception/BusinessException.java b/src/main/java/com/bamanker/spmybatisplus/exception/BusinessException.java new file mode 100644 index 0000000..b8b427e --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/exception/BusinessException.java @@ -0,0 +1,42 @@ +package com.bamanker.spmybatisplus.exception; + +import org.springframework.aot.generate.InMemoryGeneratedFiles; +import org.springframework.http.HttpStatus; + +/** + * @author bamanker + * @descriptions 自定义业务异常 + * @date 2025/12/11 23:28 + */ +public class BusinessException extends RuntimeException { + + private Integer code; + private HttpStatus httpStatus; + + public BusinessException(String message) { + super(message); + this.code = 400; + this.httpStatus = HttpStatus.BAD_REQUEST; + } + + public BusinessException(Integer code, String message) { + super(message); + this.code = code; + this.httpStatus = HttpStatus.BAD_REQUEST; + } + + public BusinessException(Integer code, String message, HttpStatus httpStatus) { + super(message); + this.code = code; + this.httpStatus = httpStatus; + } + + public Integer getCode() { + return code; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + +} diff --git a/src/main/java/com/bamanker/spmybatisplus/exception/GlobalExceptionHandler.java b/src/main/java/com/bamanker/spmybatisplus/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..4ae12fd --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/exception/GlobalExceptionHandler.java @@ -0,0 +1,141 @@ +package com.bamanker.spmybatisplus.exception; + +import com.bamanker.spmybatisplus.entity.dto.ErrorResponse; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author bamanker + * @descriptions 全局异常处理器 + * @date 2025/12/11 23:30 + */ +@ControllerAdvice +public class GlobalExceptionHandler { + private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * @descriptions + * 处理参数校验异常(@Valid注解校验失败) + * 当使用@Valid注解校验请求参数时,如果校验失败会抛出这个异常 + * @author bamanker + * @date 2025/12/11 23:33 + * @param e + * @return com.bamanker.spmybatisplus.entity.dto.ErrorResponse + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public ErrorResponse handleValidationException(MethodArgumentNotValidException e) { + Map errors = new HashMap<>(); // 存储字段错误信息 + // 遍历所有字段错误 + e.getBindingResult().getAllErrors().forEach(error -> { + String fieldName = ((FieldError) error).getField(); // 获取字段名 + String errorMessage = error.getDefaultMessage(); // 获取错误信息 + errors.put(fieldName, errorMessage); + }); + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setCode(400); + errorResponse.setMessage("参数校验失败"); + errorResponse.setErrors(errors); + errorResponse.setTimestamp(LocalDateTime.now()); + logger.warn("参数校验失败: {}", errors); + return errorResponse; + } + + /** + * 处理约束违反异常(方法参数校验失败) + */ + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public ErrorResponse handleConstraintViolationException(ConstraintViolationException e) { + Map errors = new HashMap<>(); + Set> violations = e.getConstraintViolations(); + for (ConstraintViolation violation : violations) { + String fieldName = violation.getPropertyPath().toString(); // 获取字段路径 + String errorMessage = violation.getMessage(); // 获取错误信息 + errors.put(fieldName, errorMessage); + } + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setCode(400); + errorResponse.setMessage("参数校验失败"); + errorResponse.setErrors(errors); + errorResponse.setTimestamp(LocalDateTime.now()); + return errorResponse; + } + + /** + * 处理参数异常(IllegalArgumentException) + */ + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setCode(400); + errorResponse.setMessage("参数错误: " + e.getMessage()); + errorResponse.setTimestamp(LocalDateTime.now()); + logger.warn("参数错误: {}", e.getMessage()); + return errorResponse; + } + + /** + * 处理运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public ErrorResponse handleRuntimeException(RuntimeException e) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setCode(500); + errorResponse.setMessage("服务器内部错误: " + e.getMessage()); + errorResponse.setTimestamp(LocalDateTime.now()); + // 记录完整异常堆栈 + logger.error("运行时异常: ", e); + return errorResponse; + } + + /** + * 处理所有未捕获的异常 + */ + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public ErrorResponse handleException(Exception e) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setCode(500); + errorResponse.setMessage("系统异常,请联系管理员"); + errorResponse.setTimestamp(LocalDateTime.now()); + logger.error("系统异常: ", e); // 记录完整异常堆栈 + return errorResponse; + } + + /** + * 处理自定义业务异常 + */ + @ExceptionHandler(BusinessException.class) + @ResponseBody + public ResponseEntity handleBusinessException(BusinessException e) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setCode(e.getCode()); + errorResponse.setMessage(e.getMessage()); + errorResponse.setTimestamp(LocalDateTime.now()); + logger.warn("业务异常: {}", e.getMessage()); + return ResponseEntity.status(e.getHttpStatus()).body(errorResponse); + } +} diff --git a/src/main/java/com/bamanker/spmybatisplus/mapper/UserMapper.java b/src/main/java/com/bamanker/spmybatisplus/mapper/UserMapper.java new file mode 100644 index 0000000..2c786f2 --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/mapper/UserMapper.java @@ -0,0 +1,18 @@ +package com.bamanker.spmybatisplus.mapper; + +import com.bamanker.spmybatisplus.entity.OrderVO; +import com.bamanker.spmybatisplus.entity.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +public interface UserMapper extends BaseMapper { + + @Select("select Max(TB_USER.USER_NO) from TB_USER") + String getMaxUserNo(); + + List selectOrders(); + +} diff --git a/src/main/java/com/bamanker/spmybatisplus/service/UserService.java b/src/main/java/com/bamanker/spmybatisplus/service/UserService.java new file mode 100644 index 0000000..11cc2cb --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/service/UserService.java @@ -0,0 +1,9 @@ +package com.bamanker.spmybatisplus.service; + +import com.bamanker.spmybatisplus.entity.User; +import com.baomidou.mybatisplus.extension.service.IService; + +import javax.imageio.spi.IIOServiceProvider; + +public interface UserService extends IService { +} diff --git a/src/main/java/com/bamanker/spmybatisplus/service/impl/UserServiceImpl.java b/src/main/java/com/bamanker/spmybatisplus/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..c65fffc --- /dev/null +++ b/src/main/java/com/bamanker/spmybatisplus/service/impl/UserServiceImpl.java @@ -0,0 +1,25 @@ +package com.bamanker.spmybatisplus.service.impl; + +import com.bamanker.spmybatisplus.entity.User; +import com.bamanker.spmybatisplus.mapper.UserMapper; +import com.bamanker.spmybatisplus.service.UserService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class UserServiceImpl extends ServiceImpl implements UserService { + + private final UserMapper userMapper; + + public UserServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public String getMaxUserNo() { + return userMapper.getMaxUserNo(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..a4c8253 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,96 @@ +server: + port: 8080 + compression: + enabled: true + mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json + min-response-size: 1024 + http2: + enabled: true + +spring: + datasource: + driver-class-name: com.p6spy.engine.spy.P6SpyDriver + url: jdbc:p6spy:h2:file:/data/test_mp + username: root + password: 123456 + h2: + console: + enabled: true + path: /h2-console + settings: + web-allow-others: true + sql: + init: + data-locations: classpath:/db/init.sql + mode: never + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: Asia/Shanghai + default-property-inclusion: non_null + deserialization: + fail-on-unknown-properties: false + threads: + virtual: + enabled: true + # MVC相关配置 + mvc: + # 静态资源路径映射 + static-path-pattern: /** # 静态资源访问路径模式 + # 视图解析器配置 + view: + prefix: /templates/ # 视图前缀 + suffix: .html # 视图后缀 + # 路径匹配策略 + pathmatch: + matching-strategy: ant_path_matcher # 路径匹配策略 + # 内容协商 + contentnegotiation: + favor-parameter: false # 是否支持参数方式 + + web: + resources: + static-locations: classpath:/static/,classpath:/public/,classpath:/resources/,classpath:/META-INF/resources/ + cache: + period: 3600 # 静态资源缓存时间(秒) + +# springdoc-openapi项目配置 +springdoc: + swagger-ui: + #自定义swagger前端请求路径,输入http:localhost:8080/swagger-ui会自动重定向到swagger页面 + path: /swagger-ui + tags-sorter: alpha + operations-sorter: alpha + api-docs: + path: /v3/api-docs #swagger后端请求地址 + enabled: true #是否开启文档功能 + group-configs: #分组配置,可配置多个分组 + - group: 'default' #分组名称 + paths-to-match: '/**' #配置需要匹配的路径 + packages-to-scan: com.bamanker #配置要扫描包的路径,一般配置到启动类所在的包名 + - group: 'admin-api' + paths-to-match: '/**' + packages-to-scan: com.bamanker + +# 日志配置 +logging: + level: + root: INFO + com.example.demo: DEBUG # 项目包日志级别 + pattern: + console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" + +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + map-underscore-to-camel-case: true + mapper-locations: classpath:/mapper/*Mapper.xml +# global-config: +# db-config: +# # 全局逻辑删除的实体字段名 (since 3.3.0) +# logic-delete-field: is_delete +# # 逻辑已删除值(默认为 1) +# logic-delete-value: 1 +# # 逻辑未删除值(默认为 0) +# logic-not-delete-value: 0 +# # 主键类型。AUTO-数据库ID自增,INPUT-自行设置,ASSIGN_ID-雪花算法,ASSIGN_UUID-UUID +## id-type: AUTO \ No newline at end of file diff --git a/src/main/resources/db/init.sql b/src/main/resources/db/init.sql new file mode 100644 index 0000000..0b0a5f8 --- /dev/null +++ b/src/main/resources/db/init.sql @@ -0,0 +1,35 @@ +create table TB_USER +( + ID BIGINT auto_increment, + USER_NO CHARACTER VARYING(255) not null, + NICKNAME CHARACTER VARYING(255), + EMAIL CHARACTER VARYING(255), + PHONE CHARACTER VARYING(255) not null, + GENDER TINYINT default 0 not null, + BIRTHDAY TIMESTAMP default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP, + IS_DELETE TINYINT default 0 not null, + CREATE_TIME TIMESTAMP default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + UPDATE_TIME TIMESTAMP default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + constraint TB_USER_PK + primary key (ID) +); + +comment on column TB_USER.ID is '主键'; + +comment on column TB_USER.USER_NO is '编号'; + +comment on column TB_USER.NICKNAME is '昵称'; + +comment on column TB_USER.EMAIL is '邮箱'; + +comment on column TB_USER.PHONE is '电话'; + +comment on column TB_USER.GENDER is '性别 0:男生 1:女生'; + +comment on column TB_USER.BIRTHDAY is '出生日期'; + +comment on column TB_USER.IS_DELETE is '删除标志 0:否 1:是'; + +comment on column TB_USER.CREATE_TIME is '创建时间'; + +comment on column TB_USER.UPDATE_TIME is '更新时间'; \ No newline at end of file diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..7586433 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spy.properties b/src/main/resources/spy.properties new file mode 100644 index 0000000..f035706 --- /dev/null +++ b/src/main/resources/spy.properties @@ -0,0 +1,24 @@ +#3.2.1???? +modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory +#3.2.1????????? +#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory +# ??????? +logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger +#???????? +appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger +# ???????? sql +#appender=com.p6spy.engine.spy.appender.Slf4JLogger +# ?? p6spy driver ?? +deregisterdrivers=true +# ??JDBC URL?? +useprefix=true +# ???? Log ??,????????error,info,batch,debug,statement,commit,rollback,result,resultset. +excludecategories=info,debug,result,commit,resultset +# ???? +dateformat=yyyy-MM-dd HH:mm:ss +# ??????? +#driverlist=org.h2.Driver +# ?????SQL?? +outagedetection=true +# ?SQL???? 2 ? +outagedetectioninterval=2 \ No newline at end of file diff --git a/src/test/java/com/bamanker/spmybatisplus/SpMybatisPlusApplicationTests.java b/src/test/java/com/bamanker/spmybatisplus/SpMybatisPlusApplicationTests.java new file mode 100644 index 0000000..cb371aa --- /dev/null +++ b/src/test/java/com/bamanker/spmybatisplus/SpMybatisPlusApplicationTests.java @@ -0,0 +1,134 @@ +package com.bamanker.spmybatisplus; + +import com.bamanker.spmybatisplus.entity.OrderVO; +import com.bamanker.spmybatisplus.entity.User; +import com.bamanker.spmybatisplus.enums.GenderEnum; +import com.bamanker.spmybatisplus.mapper.UserMapper; +import com.bamanker.spmybatisplus.service.UserService; +import com.bamanker.spmybatisplus.service.impl.UserServiceImpl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@SpringBootTest +class SpMybatisPlusApplicationTests { + + @Autowired + private UserMapper userMapper; + + @Autowired + private UserService userService; + + + @Test + void testAdd() { + User user = User.builder(). + userNo("003"). + nickname("haha"). + email("haha@qq.com"). + phone("13324334443"). + gender(GenderEnum.WOMEN). + birthday(new Date()). + build(); + userMapper.insert(user); + Long id = user.getId(); + System.out.println("ID: " + id); + } + + @Test + void testSave() { + + User user = User.builder(). + userNo("004"). + nickname("haha"). + phone("1231343231"). + build(); + boolean isSuccess = userService.save(user); + Long id = user.getId(); + System.out.println("isSuccess:" + isSuccess); + System.out.println("主键 ID: " + id); + + } + + /** + * @param + * @return void + * @descriptions 伪批量插入 + * @author bamanker + * @date 2025/12/10 15:34 + */ + @Test + void SaveBatch() { + List list = new ArrayList<>(); + int maxUserNo = Integer.parseInt(userMapper.getMaxUserNo()); + for (int i = 0; i < 100; i++) { + User user = User.builder() + .userNo(String.format("%04d", ++maxUserNo)) //格式化计数器的值为五位数的字符串,前面补零("%04d"表示4位数,不足4位时补零) + .nickname(RandomStringUtils.secure().nextAlphanumeric(10)) //随机字母+数字组成的字符串 + .phone("1" + RandomStringUtils.secure().nextNumeric(10)) + .gender(GenderEnum.MAN) + .build(); + list.add(user); + } + boolean isSuccess = userService.saveBatch(list); + System.out.println("isSuccess:" + isSuccess); + } + + @Test + void testList() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + List list = userMapper.selectList(queryWrapper); + System.out.println(list); + } + + @Test + void testService() { + List list = userService.list(); + list.forEach(System.out::println); + + } + + @Test + void testDelete() { + + int count = userMapper.delete(new QueryWrapper() + .lambda() + .eq(User::getUserNo, "0003") + .or() + .eq(User::getNickname, "haha")); + System.out.println("受影响的行数:" + count); + } + + @Test + void testSelectPages() { + + Page page = new Page<>(2, 10); +// page = userMapper.selectPage(page, null); + page =userService.page(page); + System.out.println("总记录数:"+page.getTotal()); + System.out.println("总共多少页:"+page.getPages()); + System.out.println("当前页码:"+page.getCurrent()); + + List list = page.getRecords(); + list.forEach(System.out::println); + + } + + @Test + void testSelectOrders() { + + List orders = userMapper.selectOrders(); + System.out.println(orders); + + } + +} diff --git a/src/test/java/com/bamanker/spmybatisplus/UserControllerTest.java b/src/test/java/com/bamanker/spmybatisplus/UserControllerTest.java new file mode 100644 index 0000000..5dd5fda --- /dev/null +++ b/src/test/java/com/bamanker/spmybatisplus/UserControllerTest.java @@ -0,0 +1,104 @@ +package com.bamanker.spmybatisplus; + +import com.bamanker.spmybatisplus.entity.dto.UserCreateRequest; +import com.bamanker.spmybatisplus.enums.GenderEnum; +import com.bamanker.spmybatisplus.mapper.UserMapper; + +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import tools.jackson.databind.ObjectMapper; + + +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +//启动了完整的Spring应用上下文 +@SpringBootTest +//自动配置了MockMvc实例 +//这种方式适用于测试涉及多个层(如控制器、服务层、持久层)的复杂场景。 +//通过这种全栈测试,你可以确保整个应用在各个层面都能正常工作,从而为实际部署提供更可靠的保障。 +@AutoConfigureMockMvc +public class UserControllerTest { + + @Autowired + private MockMvcTester mockMvc; // 注入MockMvc测试工具(Spring Boot 4新特性) + + @Autowired + private UserMapper userMapper; + + @Autowired + private ObjectMapper objectMapper; + + + + /** + * 测试hello接口 + */ + @Test + void testHello() { + // 发送GET请求到/api/users/hello + assertThat(mockMvc.get() + .uri("/api/users/hello") + .accept(MediaType.TEXT_PLAIN)) + .hasStatusOk() // 断言状态码是200 + .hasBodyTextEqualTo("Hello Spring Boot 4 MVC!"); // 断言响应内容 + } + + /** + * 测试获取用户信息接口 + */ + @Test + void testGetUser() throws Exception { + // 模拟GET请求,并指定路径参数 + assertThat(mockMvc.get() + .uri("/api/users/{id}", 1) + .accept(MediaType.APPLICATION_JSON)) + .hasStatusOk() + .bodyJson() + .extractingPath("data") + // .convertTo(User.class) + .asMap() + .contains(entry("nickname", "大哥")) + .contains(entry("gender", "MAN")) + .extractingByKey("email") + .isEqualTo("123"); + } + + @Test + void testCreateUser() throws Exception { + //创建请求对象 + int maxUserNo = Integer.parseInt(userMapper.getMaxUserNo()); + UserCreateRequest request = new UserCreateRequest(); + request.setUserNo(String.format("%04d", ++maxUserNo)); + request.setNickname("测试用户" + System.currentTimeMillis()); + request.setGender(GenderEnum.MAN); + request.setPhone("1" + RandomStringUtils.secure().nextNumeric(10)); + request.setEmail(RandomStringUtils.secure().nextAlphanumeric(8) + "@gmail.com"); + request.setBirthday(new Date()); + + //发送POST请求 + assertThat(mockMvc.post() + .uri("/api/users") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .hasStatus(201) + .bodyJson() + .extractingPath("data") + .asMap() + .containsEntry("nickname", request.getNickname()) + .containsEntry("userNo", request.getUserNo()); + + } + + +}