Initial commit

This commit is contained in:
wzy-warehouse
2026-04-07 20:00:15 +08:00
committed by GitHub
commit 2962c3ff6b
32 changed files with 2178 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf
+36
View File
@@ -0,0 +1,36 @@
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/
/.mvn/
/logs/
/src/test/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 wzy-warehouse
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+110
View File
@@ -0,0 +1,110 @@
# basic_template_not_login_back
开发基本模版——后端
# basic_template_not_login_back
## 项目介绍
`basic_template_not_login_back` 是一个包含后端的基础开发模板,旨在为快速搭建Web应用提供完整的技术栈支持。项目后端基于Spring Boot 3 + MyBatis + PostgreSQL构建,集成了国密算法(SM2/SM3/SM4)加解密功能、动态数据源、Redis缓存等核心功能,可直接作为中小型Web项目的开发起点。
## 目录结构
```
basic_template_not_login_back
├── bin # 批处理脚本
│ └── package.bat # 打包脚本
├── src
│ ├── main
│ │ ├── java/com/gis/basic_template_not_login_back
│ │ │ ├── config # 配置类
│ │ │ │ ├── CryptoProperties.java # 加解密配置属性
│ │ │ │ ├── DataSource.java # 数据源注解
│ │ │ │ ├── DataSourceAspect.java # 数据源切面
│ │ │ │ ├── DataSourceConfig.java # 数据源配置
│ │ │ │ ├── DataSourceContextHolder.java # 数据源上下文
│ │ │ │ ├── DynamicDataSource.java # 动态数据源
│ │ │ │ └── RedisConfig.java # Redis配置
│ │ │ ├── controller # 控制器层
│ │ │ │ ├── BaseController.java # 基础控制器
│ │ │ │ └── CryptoController.java # 加解密控制器
│ │ │ ├── domain # 领域对象
│ │ │ │ └── ApiResponse.java # 统一响应类
│ │ │ ├── filter # 过滤器
│ │ │ │ └── DecryptFilter.java # 解密过滤器
│ │ │ ├── mapper # MyBatis Mapper接口
│ │ │ ├── service/ex # 服务层异常
│ │ │ │ └── ServiceException.java # 业务异常类
│ │ │ ├── utils/safety # 安全工具类
│ │ │ │ ├── SM2Utils.java # SM2非对称加密工具
│ │ │ │ ├── SM3Utils.java # SM3摘要算法工具
│ │ │ │ └── SM4Utils.java # SM4对称加密工具
│ │ │ ├── vo # 视图对象
│ │ │ ├── wrapper # 请求响应包装
│ │ │ │ ├── DecryptRequestWrapper.java # 解密请求包装器
│ │ │ │ ├── EncryptResponseAdvice.java # 加密响应通知
│ │ │ │ └── Sm4KeyHolder.java # SM4密钥持有者
│ │ │ └── BasicTemplateNotLoginBackApplication.java # 启动类
│ │ └── resources # 资源文件
│ │ ├── application-database-dev.yml # 开发环境数据库配置
│ │ ├── application-database-prod.yml # 生产环境数据库配置
│ │ ├── application-dev.yml # 开发环境配置
│ │ ├── application-prod.yml # 生产环境配置
│ │ └── application.yml # 主配置文件
├── LICENSE # 许可证
└── README.md # 介绍文件
```
### 环境要求
- JDK 17+
- Maven 3.6+
- PostgreSQL 数据库
- Redis 服务器
### 项目克隆
```bash
git clone https://github.com/wzy-warehouse/basic_template_not_login_back.git
cd basic_template_not_login_back
```
### 配置说明
1. **数据库配置**: 修改 `src/main/resources/application-database-dev.yml` 中的数据库连接信息
2. **Redis配置**: 修改 `src/main/resources/application.yml` 中的Redis连接信息
3. **端口配置**: 在 `application.yml` 中修改 `server.port`(默认8080
### 启动方式
#### 方式一:Maven命令启动
```bash
# 开发环境(默认)
mvn spring-boot:run
# 生产环境
mvn spring-boot:run -Pprod
```
#### 方式二:打包后运行
```bash
# 开发环境打包
mvn clean package
# 生产环境打包
mvn clean package -Pprod
# 运行jar包
java -jar target/basic_template_not_login_back.jar
```
#### 方式三:使用批处理脚本
```bash
# Windows系统
bin\package.bat
```
## 配置文件说明
- `application.yml`: 主配置文件,公共配置
- `application-dev.yml`: 开发环境特定配置
- `application-prod.yml`: 生产环境特定配置
- `application-database-dev.yml`: 开发环境数据库配置
- `application-database-prod.yml`: 生产环境数据库配置
## 许可证
本项目基于 [MIT License](LICENSE) 开源,详情请查看LICENSE文件。
+59
View File
@@ -0,0 +1,59 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo ========================================
echo Spring Boot Project Package Script
echo ========================================
echo.
REM Set project root directory
set PROJECT_ROOT=%~dp0..
echo [1/4] Cleaning previous build...
cd /d %PROJECT_ROOT%
call mvn clean
if errorlevel 1 (
echo Clean failed!
pause
exit /b 1
)
echo Clean completed!
echo.
echo [2/4] Downloading dependencies...
call mvn dependency:resolve
if errorlevel 1 (
echo Dependency download failed!
pause
exit /b 1
)
echo Dependencies downloaded!
echo.
echo [3/4] Packaging with prod profile...
call mvn package -DskipTests -Pprod
if errorlevel 1 (
echo Package failed!
pause
exit /b 1
)
echo Package completed!
echo.
echo [4/4] Searching for generated jar file...
for /f "delims=" %%i in ('dir /b /od "%PROJECT_ROOT%\target\*.jar"') do (
set JAR_FILE=%%i
)
if defined JAR_FILE (
echo ========================================
echo Package Success!
echo Jar file location: %PROJECT_ROOT%\target\%JAR_FILE%
echo ========================================
) else (
echo Jar file not found!
)
echo.
pause
Vendored
+295
View File
@@ -0,0 +1,295 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"
Vendored
+189
View File
@@ -0,0 +1,189 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+197
View File
@@ -0,0 +1,197 @@
<?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>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis.spring.version>3.0.5</mybatis.spring.version>
<druid.version>1.2.27</druid.version>
<postgresql.version>42.7.8</postgresql.version>
<lombok.version>1.18.42</lombok.version>
<bcprov-jdk15to18.version>1.82</bcprov-jdk15to18.version>
<fastjson.version>2.0.60</fastjson.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.13</version>
<relativePath/>
</parent>
<groupId>com.gis</groupId>
<artifactId>basic_template_not_login_back</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>basic_template_not_login_back</name>
<description>basic_template_not_login_back</description>
<dependencies>
<!-- Spring Boot Web 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Spring Boot 配置处理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- MyBatis 整合 Spring Boot -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!-- 数据库连接池 Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- postgresql驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- WebSocket 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- 提供 Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 国密算法 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>${bcprov-jdk15to18.version}</version>
</dependency>
<!-- Spring Boot Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- Spring Boot AOP 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 测试框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<!-- 开发环境(默认) -->
<profile>
<id>dev</id>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!-- 生产环境 -->
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
<build>
<!-- 配置资源文件过滤,支持Maven profile变量替换 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.yml</include>
<include>**/*.yaml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.yml</exclude>
<exclude>**/*.yaml</exclude>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<source>17</source>
<target>17</target>
<release>17</release>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<finalName>basic_template_not_login_back</finalName>
</build>
</project>
@@ -0,0 +1,29 @@
package com.gis.basic_template_not_login_back;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* 后台启动入口
* 启动时过滤DataSourceAutoConfiguration,避免数据源自动配置
*/
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan("com.gis.basic_template_not_login_back.mapper") // 扫描MyBatis的Mapper接口
public class BasicTemplateNotLoginBackApplication {
// 使用非静态变量
@Value("${server.port}")
private Integer port;
public static void main(String[] args) {
// 启动Spring Boot应用并获取应用上下文
var context = SpringApplication.run(BasicTemplateNotLoginBackApplication.class, args);
// 从上下文中获取当前实例
BasicTemplateNotLoginBackApplication app = context.getBean(BasicTemplateNotLoginBackApplication.class);
System.out.println("后端服务启动成功!访问地址: http://localhost:" + app.port);
}
}
@@ -0,0 +1,28 @@
package com.gis.basic_template_not_login_back.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* 加解密配置属性类
* 从application.yml中读取safety.crypto配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "safety.crypto")
public class CryptoProperties {
/**
* 响应无需加密的路径列表
*/
private List<String> noEncryptPaths = Collections.emptyList();
/**
* 请求无需解密的路径列表
*/
private List<String> noDecryptPaths = Collections.emptyList();
}
@@ -0,0 +1,20 @@
package com.gis.basic_template_not_login_back.config;
import java.lang.annotation.*;
/**
* 数据源切换注解
* 可用于类或方法上,指定使用的数据源
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 数据源名称
* 可选值:master(主库)、slave等
* @return 数据源名称
*/
String value() default "master";
}
@@ -0,0 +1,27 @@
package com.gis.basic_template_not_login_back.config;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(1)
@Slf4j
public class DataSourceAspect {
@Around("@annotation(dataSource) || @within(dataSource)")
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
try {
String dsName = dataSource.value();
log.debug("切换数据源: {}", dsName);
DataSourceContextHolder.setDataSource(dsName);
return point.proceed();
} finally {
DataSourceContextHolder.clearDataSource();
}
}
}
@@ -0,0 +1,39 @@
package com.gis.basic_template_not_login_back.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource master() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slave() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dataSource() {
DynamicDataSource ds = new DynamicDataSource();
Map<Object, Object> map = new HashMap<>();
map.put("master", master());
map.put("slave", slave());
ds.setTargetDataSources(map);
ds.setDefaultTargetDataSource(master());
return ds;
}
}
@@ -0,0 +1,42 @@
package com.gis.basic_template_not_login_back.config;
/**
* 数据源上下文持有者
* 使用ThreadLocal存储当前线程使用的数据源名称
*/
public class DataSourceContextHolder {
/**
* 默认数据源名称
*/
private static final String DEFAULT_DATASOURCE = "master";
/**
* ThreadLocal存储数据源名称
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源名称
* @param dataSourceName 数据源名称
*/
public static void setDataSource(String dataSourceName) {
CONTEXT_HOLDER.set(dataSourceName);
}
/**
* 获取数据源名称
* @return 数据源名称
*/
public static String getDataSource() {
String dataSource = CONTEXT_HOLDER.get();
return dataSource == null ? DEFAULT_DATASOURCE : dataSource;
}
/**
* 清除数据源名称
*/
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
@@ -0,0 +1,15 @@
package com.gis.basic_template_not_login_back.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源路由
* 根据DataSourceContextHolder中设置的数据源名称,动态选择数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
@@ -0,0 +1,32 @@
package com.gis.basic_template_not_login_back.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 字符串序列化器(key 用字符串存储)
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// JSON 序列化器(value 用 JSON 存储,支持对象)
Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(new ObjectMapper(), Object.class);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}
@@ -0,0 +1,23 @@
package com.gis.basic_template_not_login_back.controller;
import com.gis.basic_template_not_login_back.domain.ApiResponse;
import com.gis.basic_template_not_login_back.service.ex.ServiceException;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* 基础controller类,所有controller层对象必须继承这个类
*/
public class BaseController{
// 用于统一处理应用层抛出的异常
@ExceptionHandler(ServiceException.class)
public ApiResponse<Void> handleServiceException(Throwable e) {
return ApiResponse.error(e.getMessage());
}
// 用于统一处理其余抛出的异常
@ExceptionHandler(RuntimeException.class)
public ApiResponse<Void> handleException(Throwable e) {
return ApiResponse.error(e.getMessage());
}
}
@@ -0,0 +1,52 @@
package com.gis.basic_template_not_login_back.controller;
import com.gis.basic_template_not_login_back.domain.ApiResponse;
import com.gis.basic_template_not_login_back.utils.safety.SM2Utils;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/crypto")
public class CryptoController extends BaseController {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Value("${safety.sm2.global}")
private String sm2KeyPairRedisKey;
/**
* 初始化SM2密钥对
*/
@PostConstruct
public void initSm2KeyPair() {
Object sm2KeyPairObj = redisTemplate.opsForValue().get(sm2KeyPairRedisKey);
if (sm2KeyPairObj == null) {
Map<String, String> sm2KeyPair = SM2Utils.generateKeyPair();
redisTemplate.opsForValue().set(sm2KeyPairRedisKey, sm2KeyPair);
System.out.println("SM2密钥对已生成并存储到Redis");
}
}
/**
* 获取SM2公钥
*/
@GetMapping("/sm2/public-key")
@SuppressWarnings("unchecked")
public ApiResponse<Map<String, String>> getSm2PublicKey() {
Map<String, String> sm2KeyPair = (Map<String, String>) redisTemplate.opsForValue().get(sm2KeyPairRedisKey);
if (sm2KeyPair == null) {
throw new RuntimeException("SM2密钥对未初始化");
}
Map<String, String> result = new HashMap<>();
result.put("publicKey", sm2KeyPair.get("publicKey"));
return ApiResponse.ok(result);
}
}
@@ -0,0 +1,65 @@
package com.gis.basic_template_not_login_back.domain;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class ApiResponse<T> implements Serializable {
@Serial
private static final long serialVersionUID = -7318963194081396360L;
private int code;
private String message;
private T data;
public ApiResponse() {}
public ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public ApiResponse(int code, String message) {
this.code = code;
this.message = message;
}
// 成功响应 - 无数据
public static ApiResponse<Void> ok() {
return new ApiResponse<>(200, "success");
}
// 成功响应 - 带数据
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(200, "success", data);
}
// 成功响应 - 自定义消息和数据
public static <T> ApiResponse<T> ok(String message, T data) {
return new ApiResponse<>(200, message, data);
}
// 错误响应 - 默认错误
public static ApiResponse<Void> error() {
return new ApiResponse<>(500, "error");
}
// 错误响应 - 自定义错误消息
public static ApiResponse<Void> error(String message) {
return new ApiResponse<>(500, message);
}
// 错误响应 - 自定义状态码和错误消息
public static ApiResponse<Void> error(Integer code, String message) {
return new ApiResponse<>(code, message);
}
// 错误相应,自定义所有信息
public static <T> ApiResponse<T> error(Integer code, String message, T data) {
return new ApiResponse<>(code, message, data);
}
}
@@ -0,0 +1,292 @@
package com.gis.basic_template_not_login_back.filter;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.gis.basic_template_not_login_back.config.CryptoProperties;
import com.gis.basic_template_not_login_back.utils.safety.SM2Utils;
import com.gis.basic_template_not_login_back.utils.safety.SM4Utils;
import com.gis.basic_template_not_login_back.wrapper.Sm4KeyHolder;
import jakarta.annotation.Resource;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 请求解密过滤器
*/
@WebFilter(urlPatterns = "/*")
@Order(1)
@Component
public class DecryptFilter implements Filter {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Value("${safety.sm2.global}")
private String sm2KeyPairRedisKey;
@Resource
private CryptoProperties cryptoProperties;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String requestUri = request.getRequestURI();
// 检查是否为无需解密的接口
if (isNoDecryptPath(requestUri)) {
chain.doFilter(request, servletResponse);
return;
}
try {
// 提取加密参数
String[] encryptedParams = extractEncryptedParams(request);
String encryptedData = encryptedParams[0];
String sm4KeyEncrypted = encryptedParams[1];
// 校验加密参数(仅校验sm4KeyEncrypted,确保必传)
if (!StringUtils.hasText(sm4KeyEncrypted)) {
throw new IllegalArgumentException("加密参数缺失(sm4KeyEncrypted");
}
// 解密SM4密钥
String sm2PrivateKey = getSm2PrivateKey();
String sm4Key = SM2Utils.decrypt(sm2PrivateKey, sm4KeyEncrypted);
// 存储SM4密钥
Sm4KeyHolder.setSm4Key(sm4Key);
// 仅当encryptedData有实际内容时才解密(排除null和空字符串)
String decryptedData = null;
if (StringUtils.hasText(encryptedData)) {
decryptedData = SM4Utils.decrypt(sm4Key, encryptedData);
}
// 包装解密后的请求(兼容空数据场景)
DecryptRequestWrapper wrappedRequest = wrapDecryptedRequest(request, decryptedData);
chain.doFilter(wrappedRequest, servletResponse);
} catch (Exception e) {
handleDecryptError(servletResponse, e);
}
}
/**
* 检查是否为无需解密的路径
*/
private boolean isNoDecryptPath(String requestUri) {
for (String path : cryptoProperties.getNoDecryptPaths()) {
if (requestUri.contains(path)) {
return true;
}
}
return false;
}
/**
* 提取加密参数
*/
private String[] extractEncryptedParams(HttpServletRequest request) throws IOException {
String encryptedData = null;
String sm4KeyEncrypted = null;
String method = request.getMethod();
String contentType = request.getContentType();
if ("GET".equalsIgnoreCase(method)) {
// GET请求从Query参数提取(允许参数为空字符串)
encryptedData = request.getParameter("encryptedData");
sm4KeyEncrypted = request.getParameter("sm4KeyEncrypted");
} else {
// POST/PUT请求从Body提取
if (contentType == null) {
throw new IllegalArgumentException("请求Content-Type不能为空");
}
if (contentType.contains("application/json")) {
String jsonBody = readRequestBody(request);
// 空JSON体处理(避免解析空字符串报错)
if (StringUtils.hasText(jsonBody)) {
Map<String, Object> bodyMap = JSON.parseObject(jsonBody, new TypeReference<Map<String, Object>>() {});
encryptedData = (String) bodyMap.get("encryptedData");
sm4KeyEncrypted = (String) bodyMap.get("sm4KeyEncrypted");
}
} else if (contentType.contains("multipart/form-data")) {
// FormData从表单参数提取
encryptedData = request.getParameter("encryptedData");
sm4KeyEncrypted = request.getParameter("sm4KeyEncrypted");
} else if (contentType.contains("application/x-www-form-urlencoded")) {
encryptedData = request.getParameter("encryptedData");
sm4KeyEncrypted = request.getParameter("sm4KeyEncrypted");
} else {
throw new IllegalArgumentException("不支持的Content-Type: " + contentType);
}
}
return new String[]{encryptedData, sm4KeyEncrypted};
}
/**
* 获取SM2私钥
*/
@SuppressWarnings("unchecked")
private String getSm2PrivateKey() {
Map<String, String> sm2KeyPair = (Map<String, String>) redisTemplate.opsForValue().get(sm2KeyPairRedisKey);
if (sm2KeyPair == null || !sm2KeyPair.containsKey("privateKey")) {
throw new RuntimeException("Redis中未找到SM2私钥,请先初始化密钥对");
}
return sm2KeyPair.get("privateKey");
}
/**
* 包装解密后的请求(兼容空数据)
*/
private DecryptRequestWrapper wrapDecryptedRequest(HttpServletRequest request, String decryptedData) {
if ("GET".equalsIgnoreCase(request.getMethod())) {
// GET请求:解析为参数Map(空数据返回空Map)
Map<String, String[]> originalParams = parseGetParams(decryptedData);
DecryptRequestWrapper wrapper = new DecryptRequestWrapper(request, new byte[0]);
wrapper.setDecryptedParams(originalParams);
return wrapper;
} else {
// POST请求:空数据转换为空字节数组(避免null)
byte[] decryptedBody = StringUtils.hasText(decryptedData)
? decryptedData.getBytes(StandardCharsets.UTF_8)
: new byte[0];
return new DecryptRequestWrapper(request, decryptedBody);
}
}
/**
* 解析GET参数(兼容空数据)
*/
private Map<String, String[]> parseGetParams(String decryptedData) {
// 空数据直接返回空Map,避免JSON解析报错
if (!StringUtils.hasText(decryptedData)) {
return new HashMap<>();
}
Map<String, Object> paramMap = JSON.parseObject(decryptedData);
Map<String, String[]> result = new HashMap<>(paramMap.size());
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value == null) {
result.put(key, new String[0]);
} else if (value instanceof String) {
result.put(key, new String[]{(String) value});
} else {
result.put(key, new String[]{value.toString()});
}
}
return result;
}
/**
* 读取请求体
*/
private String readRequestBody(HttpServletRequest request) throws IOException {
try (ServletInputStream is = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
}
/**
* 处理解密错误
*/
private void handleDecryptError(ServletResponse servletResponse, Exception e) throws IOException {
servletResponse.setContentType("application/json;charset=UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
Map<String, Object> errorResult = new HashMap<>();
errorResult.put("code", 500);
errorResult.put("msg", "请求解密失败: " + e.getMessage());
errorResult.put("success", false);
PrintWriter writer = servletResponse.getWriter();
writer.write(JSON.toJSONString(errorResult));
writer.flush();
}
/**
* 自定义请求包装类
*/
public static class DecryptRequestWrapper extends jakarta.servlet.http.HttpServletRequestWrapper {
private final byte[] decryptedBody;
@Setter
private Map<String, String[]> decryptedParams;
public DecryptRequestWrapper(HttpServletRequest request, byte[] decryptedBody) {
super(request);
this.decryptedBody = decryptedBody;
this.decryptedParams = new HashMap<>(request.getParameterMap());
}
@Override
public ServletInputStream getInputStream() {
ByteArrayInputStream bis = new ByteArrayInputStream(decryptedBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return bis.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
// 无需实现
}
@Override
public int read() {
return bis.read();
}
};
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
}
@Override
public String getParameter(String name) {
String[] values = decryptedParams.get(name);
return values != null && values.length > 0 ? values[0] : null;
}
@Override
public Map<String, String[]> getParameterMap() {
return decryptedParams;
}
@Override
public String[] getParameterValues(String name) {
return decryptedParams.get(name);
}
}
}
@@ -0,0 +1,25 @@
package com.gis.basic_template_not_login_back.service.ex;
/**
* 业务层所有自定义异常父类
*/
public class ServiceException extends RuntimeException {
public ServiceException() {
}
public ServiceException(String message) {
super(message);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
public ServiceException(Throwable cause) {
super(cause);
}
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
@@ -0,0 +1,106 @@
package com.gis.basic_template_not_login_back.utils.safety;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
/**
* SM2工具类(与前端gm-crypto兼容,加解密使用Hex编码)
*/
public class SM2Utils {
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
private static final X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
private static final ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
/**
* 生成SM2密钥对
*/
public static Map<String, String> generateKeyPair() {
try {
ECKeyPairGenerator generator = new ECKeyPairGenerator();
ECKeyGenerationParameters genParams = new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom());
generator.init(genParams);
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
// 公钥:非压缩格式(64字节,包含0x04前缀共65字节)
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
String publicKeyHex = Hex.toHexString(publicKey.getQ().getEncoded(false));
// 私钥:32字节Hex字符串
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
String privateKeyHex = privateKey.getD().toString(16);
// 补零到64位
privateKeyHex = String.format("%64s", privateKeyHex).replace(' ', '0');
Map<String, String> keyMap = new HashMap<>(2);
keyMap.put("publicKey", publicKeyHex);
keyMap.put("privateKey", privateKeyHex);
return keyMap;
} catch (Exception e) {
throw new RuntimeException("生成SM2密钥对失败: " + e.getMessage(), e);
}
}
/**
* SM2公钥加密(返回Hex字符串)
*/
public static String encrypt(String publicKeyHex, String plaintext) {
try {
// 解析带0x04前缀的公钥(65字节)
byte[] publicKeyBytes = Hex.decode(publicKeyHex);
ECPublicKeyParameters publicKey = new ECPublicKeyParameters(x9ECParameters.getCurve().decodePoint(publicKeyBytes), ecDomainParameters);
// 初始化加密器(C1C3C2模式)
org.bouncycastle.crypto.engines.SM2Engine engine = new org.bouncycastle.crypto.engines.SM2Engine((org.bouncycastle.crypto.engines.SM2Engine.Mode.C1C3C2));
engine.init(true, new ParametersWithRandom(publicKey, new SecureRandom()));
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
byte[] encryptedBytes = engine.processBlock(plaintextBytes, 0, plaintextBytes.length);
// 加密结果转为Hex字符串
return Hex.toHexString(encryptedBytes);
} catch (Exception e) {
throw new RuntimeException("SM2加密失败: " + e.getMessage(), e);
}
}
/**
* SM2私钥解密(接收Hex字符串密文)
*/
public static String decrypt(String privateKeyHex, String ciphertextHex) {
try {
// 解析私钥
byte[] privateKeyBytes = Hex.decode(privateKeyHex);
ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(1, privateKeyBytes), ecDomainParameters);
// 解码Hex格式密文
byte[] ciphertextBytes = Hex.decode(ciphertextHex);
// 初始化解密器
org.bouncycastle.crypto.engines.SM2Engine engine = new org.bouncycastle.crypto.engines.SM2Engine(org.bouncycastle.crypto.engines.SM2Engine.Mode.C1C3C2);
engine.init(false, privateKey);
byte[] decryptedBytes = engine.processBlock(ciphertextBytes, 0, ciphertextBytes.length);
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("SM2解密失败: " + e.getMessage(), e);
}
}
}
@@ -0,0 +1,46 @@
package com.gis.basic_template_not_login_back.utils.safety;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
/**
* SM3算法工具类(纯算法实现,不依赖业务逻辑)
* 仅负责:计算字节数组的SM3哈希值、字节数组与十六进制转换
*/
public class SM3Utils {
/**
* 计算字节数组的SM3哈希值
* @param input 输入字节数组(可任意数据,如盐值+密码的组合)
* @return SM3哈希值(32字节)
*/
public static byte[] hash(byte[] input) {
if (input == null || input.length == 0) {
throw new IllegalArgumentException("输入字节数组不能为空");
}
SM3Digest digest = new SM3Digest();
digest.update(input, 0, input.length);
byte[] hashBytes = new byte[digest.getDigestSize()];
digest.doFinal(hashBytes, 0);
return hashBytes;
}
/**
* 字节数组转16进制字符串(用于哈希值可视化)
*/
public static String toHex(byte[] bytes) {
if (bytes == null) {
return null;
}
return Hex.toHexString(bytes);
}
/**
* 16进制字符串转字节数组(反向操作)
*/
public static byte[] fromHex(String hexStr) {
if (hexStr == null || hexStr.isEmpty()) {
return null;
}
return Hex.decode(hexStr);
}
}
@@ -0,0 +1,82 @@
package com.gis.basic_template_not_login_back.utils.safety;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;
import java.nio.charset.StandardCharsets;
/**
* SM4工具类(ECB模式,与前端兼容,加解密使用Hex编码)
*/
public class SM4Utils {
/**
* SM4加密(ECB模式,返回Hex字符串)
*/
public static String encrypt(String keyHex, String plaintext) {
try {
// 解析密钥(16字节=32位Hex字符串)
byte[] key = Hex.decode(keyHex);
if (key.length != 16) {
throw new IllegalArgumentException("SM4密钥必须是16字节(32位Hex字符串)");
}
// 初始化加密器(ECB模式 + PKCS7填充)
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new SM4Engine(),
new PKCS7Padding()
);
CipherParameters params = new KeyParameter(key);
cipher.init(true, params);
// 处理明文
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
byte[] output = new byte[cipher.getOutputSize(plaintextBytes.length)];
int length = cipher.processBytes(plaintextBytes, 0, plaintextBytes.length, output, 0);
length += cipher.doFinal(output, length);
// 加密结果转为Hex字符串
return Hex.toHexString(output, 0, length);
} catch (Exception e) {
throw new RuntimeException("SM4加密失败: " + e.getMessage(), e);
}
}
/**
* SM4解密(ECB模式,接收Hex字符串密文)
*/
public static String decrypt(String keyHex, String ciphertextHex) {
try {
// 解析密钥(16字节=32位Hex字符串)
byte[] key = Hex.decode(keyHex);
if (key.length != 16) {
throw new IllegalArgumentException("SM4密钥必须是16字节(32位Hex字符串)");
}
// 解码Hex格式密文
byte[] ciphertextBytes = Hex.decode(ciphertextHex);
// 初始化解密器
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new SM4Engine(),
new PKCS7Padding()
);
CipherParameters params = new KeyParameter(key);
cipher.init(false, params);
// 处理密文
byte[] output = new byte[cipher.getOutputSize(ciphertextBytes.length)];
int length = cipher.processBytes(ciphertextBytes, 0, ciphertextBytes.length, output, 0);
length += cipher.doFinal(output, length);
// 解密结果转为字符串
return new String(output, 0, length, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("SM4解密失败: " + e.getMessage(), e);
}
}
}
@@ -0,0 +1,68 @@
package com.gis.basic_template_not_login_back.wrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.Setter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 解密拦截器
*/
public class DecryptRequestWrapper extends HttpServletRequestWrapper {
// 缓存解密后的请求体(用于POST等带Body的请求)
private final byte[] decryptedBody;
// 用于更新解密后的参数(GET场景)
// 缓存解密后的参数(用于GET等带Query参数的请求)
@Setter
private Map<String, String[]> decryptedParams;
public DecryptRequestWrapper(HttpServletRequest request, byte[] decryptedBody) {
super(request);
this.decryptedBody = decryptedBody;
this.decryptedParams = new HashMap<>(request.getParameterMap());
}
// 重写输入流,返回解密后的Body
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(decryptedBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return bis.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {}
@Override
public int read() throws IOException {
return bis.read();
}
};
}
// 重写参数获取方法,返回解密后的参数(用于GET)
@Override
public String getParameter(String name) {
String[] values = decryptedParams.get(name);
return values != null && values.length > 0 ? values[0] : null;
}
@Override
public Map<String, String[]> getParameterMap() {
return decryptedParams;
}
}
@@ -0,0 +1,77 @@
package com.gis.basic_template_not_login_back.wrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gis.basic_template_not_login_back.config.CryptoProperties;
import com.gis.basic_template_not_login_back.utils.safety.SM4Utils;
import jakarta.annotation.Resource;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import jakarta.servlet.http.HttpServletRequest;
/**
* 响应数据加密拦截器
*/
@ControllerAdvice
public class EncryptResponseAdvice implements ResponseBodyAdvice<Object> {
@Resource
private ObjectMapper objectMapper;
@Resource
private CryptoProperties cryptoProperties;
/**
* 判断是否需要加密:排除特定路径,其余全部加密
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 获取当前请求的URI
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return false; // 非Web请求场景,不加密
}
HttpServletRequest request = attributes.getRequest();
String requestUri = request.getRequestURI();
// 检查是否为无需加密的路径
for (String path : cryptoProperties.getNoEncryptPaths()) {
if (requestUri.contains(path)) {
return false; // 排除路径,不加密
}
}
// 其余路径均需要加密
return true;
}
/**
* 响应体加密逻辑(保持不变)
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
try {
String sm4Key = Sm4KeyHolder.getSm4Key();
if (sm4Key == null || sm4Key.length() != 32) {
throw new RuntimeException("SM4密钥不存在或格式错误,无法加密响应");
}
String plaintext = objectMapper.writeValueAsString(body);
String encryptedText = SM4Utils.encrypt(sm4Key, plaintext);
return encryptedText;
} catch (Exception e) {
throw new RuntimeException("响应数据加密失败: " + e.getMessage(), e);
} finally {
Sm4KeyHolder.clear(); // 清除线程本地存储,避免内存泄漏
}
}
}
@@ -0,0 +1,24 @@
package com.gis.basic_template_not_login_back.wrapper;
/**
* SM4密钥存储
*/
public class Sm4KeyHolder {
// 线程本地存储SM4密钥(Hex格式,32位字符串)
private static final ThreadLocal<String> SM4_KEY_HOLDER = new ThreadLocal<>();
// 设置当前线程的SM4密钥
public static void setSm4Key(String sm4Key) {
SM4_KEY_HOLDER.set(sm4Key);
}
// 获取当前线程的SM4密钥
public static String getSm4Key() {
return SM4_KEY_HOLDER.get();
}
// 清除线程本地存储(避免内存泄漏)
public static void clear() {
SM4_KEY_HOLDER.remove();
}
}
@@ -0,0 +1,38 @@
spring:
datasource:
# Druid 公共配置
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
# 主库
master:
url: jdbc:postgresql://47.92.216.173:7654/xian?characterEncoding=utf8&TimeZone=Asia/Shanghai
username: postgres
password: zhangsan
driver-class-name: org.postgresql.Driver
# 从库
slave:
url: jdbc:postgresql://47.92.216.173:7654/assess_disaster?characterEncoding=utf8&TimeZone=Asia/Shanghai
username: postgres
password: zhangsan
driver-class-name: org.postgresql.Driver
@@ -0,0 +1,35 @@
spring:
datasource:
# Druid 公共配置
druid:
initial-size: 10
min-idle: 10
max-active: 50
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: false
# 主库
master:
url: jdbc:postgresql://10.22.245.138:54321/xianDC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: zaihailian
password: XAYJ@gis2603
driver-class-name: org.postgresql.Driver
# 从库
slave:
url: jdbc:postgresql://10.22.245.138:54321/xianDCAccess?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: zaihailian
password: XAYJ@gis2603
driver-class-name: org.postgresql.Driver
+42
View File
@@ -0,0 +1,42 @@
# 开发环境配置
spring:
config:
import: classpath:application-database-dev.yml
# redis
data:
redis:
host: 47.92.216.173
port: 7655
password: zhangsan
database: 0
connect-timeout: 3000ms
# 日志配置
logging:
level:
root: INFO
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
file: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
file:
name: ./logs/springboot-app.log
logback:
rollingpolicy:
file-name-pattern: ./logs/springboot-app-%d{yyyy-MM-dd}.%i.log
max-file-size: 100MB
max-history: 7
total-size-cap: 1GB
clean-history-on-start: true
# 安全配置
safety:
# 加解密过滤路径配置
crypto:
# 响应无需加密的路径
no-encrypt-paths:
- /crypto/sm2/public-key
- /druid
# 请求无需解密的路径
no-decrypt-paths:
- /crypto/sm2/public-key
- /druid
+41
View File
@@ -0,0 +1,41 @@
# 生产环境配置
spring:
config:
import: classpath:application-database-prod.yml
# redis
data:
redis:
host: 10.22.245.246
port: 6379
password: XAYJ@gis2603
database: 0
connect-timeout: 3000ms
# 日志配置
logging:
level:
root: WARN
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
file: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
file:
name: ./logs/springboot-app.log
logback:
rollingpolicy:
file-name-pattern: ./logs/springboot-app-%d{yyyy-MM-dd}.%i.log
max-file-size: 100MB
max-history: 7
total-size-cap: 1GB
clean-history-on-start: true
# 安全配置
safety:
# 加解密过滤路径配置
crypto:
# 响应无需加密的路径
no-encrypt-paths:
- /crypto/sm2/public-key
# 请求无需解密的路径
no-decrypt-paths:
- /crypto/sm2/public-key
+21
View File
@@ -0,0 +1,21 @@
# 端口
server:
port: 8080
# 激活的配置文件(默认dev,打包时会被Maven profile替换)
spring:
profiles:
active: @spring.profiles.active@
# MyBatis 配置
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.gis.basic_template_not_login_back.entity
configuration:
map-underscore-to-camel-case: true
# 安全配置
safety:
sm2:
# sm2公钥在redis存储名
global: 'sm2:keypair:global'