v3.0.0 springboot3+jdk17版本
This commit is contained in:
1 parent
3aa2c942dc
commit
b3ee447f8e
3676 files changed
+64249
-370267
No files matched your search
@@ -0,0 +1,53 @@
|
|||||||
|
######################################################################
|
||||||
|
# Build Tools
|
||||||
|
|
||||||
|
.gradle
|
||||||
|
/build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
.flattened-pom.xml
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# IDE
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
nbproject/private/
|
||||||
|
build/*
|
||||||
|
nbbuild/
|
||||||
|
dist/
|
||||||
|
nbdist/
|
||||||
|
.nb-gradle/
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Others
|
||||||
|
*.log
|
||||||
|
*.xml.versionsBackup
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
!*/build/*.java
|
||||||
|
!*/build/*.html
|
||||||
|
!*/build/*.xml
|
||||||
|
|
||||||
|
### JRebel ###
|
||||||
|
rebel.xml
|
||||||
|
|
||||||
|
application-my.yaml
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
意向订餐系统,类似肯德基点餐小程序模式,支持多门店模式,基础技术Java,uniapp(支持H5、微信小程序)
|
||||||
|
采用当前流行技术组合的前后端分离点餐系统: SpringBoot3+jdk17、Spring Security OAuth2、MybatisPlus、SpringSecurity、jwt、redis、Vue3的前后端分离的系统,
|
||||||
|
包含店铺管理、积分兑换、云小票打印、图片素材库、订单管理、多规格sku、积分、优惠券、充值、多门店、微信公众号等功能,更适合企业或个人二次开发.
|
||||||
File renamed without changes.
@@ -0,0 +1,152 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<modules>
|
||||||
|
<module>yshop-dependencies</module>
|
||||||
|
<module>yshop-framework</module>
|
||||||
|
<!-- Server 主项目 -->
|
||||||
|
<module>yshop-server</module>
|
||||||
|
<!-- 各种 module 拓展 -->
|
||||||
|
<module>yshop-module-system</module>
|
||||||
|
<module>yshop-module-infra</module>
|
||||||
|
<module>yshop-module-pay</module>
|
||||||
|
<module>yshop-module-mp</module>
|
||||||
|
<module>yshop-module-mall</module>
|
||||||
|
<module>yshop-module-message</module>
|
||||||
|
<module>yshop-module-score</module>
|
||||||
|
<module>yshop-module-express</module>
|
||||||
|
<module>yshop-module-marketing</module>
|
||||||
|
<module>yshop-module-member</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>yshop项目基础脚手架</description>
|
||||||
|
<url>https://gitee.com/guchengwuyue/yshop-drink</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<revision>3.0.0</revision>
|
||||||
|
<!-- Maven 相关 -->
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
|
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||||
|
<maven-surefire-plugin.version>3.2.2</maven-surefire-plugin.version>
|
||||||
|
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
|
||||||
|
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
|
||||||
|
<!-- 看看咋放到 bom 里 -->
|
||||||
|
<lombok.version>1.18.30</lombok.version>
|
||||||
|
<spring.boot.version>3.2.2</spring.boot.version>
|
||||||
|
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-dependencies</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<!-- maven-surefire-plugin 插件,用于运行单元测试。 -->
|
||||||
|
<!-- 注意,需要使用 3.0.X+,因为要支持 Junit 5 版本 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven-surefire-plugin.version}</version>
|
||||||
|
</plugin>
|
||||||
|
<!-- maven-compiler-plugin 插件,解决 spring-boot-configuration-processor + Lombok + MapStruct 组合 -->
|
||||||
|
<!-- https://stackoverflow.com/questions/33483697/re-run-spring-boot-configuration-annotation-processor-to-update-generated-metada -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven-compiler-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
<version>${mapstruct.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
<!-- 编译参数写在 arg 内,解决 Spring Boot 3.2 的 Parameter Name Discovery 问题 -->
|
||||||
|
<debug>false</debug>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-parameters</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>flatten-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<!-- 统一 revision 版本 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>flatten-maven-plugin</artifactId>
|
||||||
|
<version>${flatten-maven-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||||||
|
<updatePomFile>true</updatePomFile>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>flatten</goal>
|
||||||
|
</goals>
|
||||||
|
<id>flatten</id>
|
||||||
|
<phase>process-resources</phase>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>clean</goal>
|
||||||
|
</goals>
|
||||||
|
<id>flatten.clean</id>
|
||||||
|
<phase>clean</phase>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<!-- 使用 huawei / aliyun 的 Maven 源,提升下载速度 -->
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>huaweicloud</id>
|
||||||
|
<name>huawei</name>
|
||||||
|
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>aliyunmaven</id>
|
||||||
|
<name>aliyun</name>
|
||||||
|
<url>https://maven.aliyun.com/repository/public</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Docker Build & Up
|
||||||
|
|
||||||
|
目标: 快速部署体验系统,帮助了解系统之间的依赖关系。
|
||||||
|
依赖:docker compose v2,删除`name: yshop-system`,降低`version`版本为`3.3`以下,支持`docker-compose`。
|
||||||
|
|
||||||
|
## 功能文件列表
|
||||||
|
|
||||||
|
```text
|
||||||
|
.
|
||||||
|
├── Docker-HOWTO.md
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── docker.env <-- 提供docker-compose环境变量配置
|
||||||
|
├── yshop-server
|
||||||
|
│ └── Dockerfile
|
||||||
|
└── yshop-ui-admin
|
||||||
|
├── .dockerignore
|
||||||
|
├── Dockerfile
|
||||||
|
└── nginx.conf <-- 提供基础配置,gzip压缩、api转发
|
||||||
|
```
|
||||||
|
|
||||||
|
## 构建 jar 包
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 创建maven缓存volume
|
||||||
|
docker volume create --name yshop-maven-repo
|
||||||
|
|
||||||
|
docker run -it --rm --name yshop-maven \
|
||||||
|
-v yshop-maven-repo:/root/.m2 \
|
||||||
|
-v $PWD:/usr/src/mymaven \
|
||||||
|
-w /usr/src/mymaven \
|
||||||
|
maven mvn clean install package '-Dmaven.test.skip=true'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 构建启动服务
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker compose --env-file docker.env up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
首次运行会自动构建容器。可以通过`docker compose build [service]`来手动构建所有或某个docker镜像
|
||||||
|
|
||||||
|
`--env-file docker.env`为可选参数,只是展示了通过`.env`文件配置容器启动的环境变量,`docker-compose.yml`本身已经提供足够的默认参数来正常运行系统。
|
||||||
|
|
||||||
|
## 服务器的宿主机端口映射
|
||||||
|
|
||||||
|
- admin ui: http://localhost:8080
|
||||||
|
- api server: http://localhost:48080
|
||||||
|
- mysql: root/123456, port: 3306
|
||||||
|
- redis: port: 6379
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
version: "3.4"
|
||||||
|
|
||||||
|
name: yshop-system
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
container_name: yshop-mysql
|
||||||
|
image: mysql:8
|
||||||
|
restart: unless-stopped
|
||||||
|
tty: true
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-yixiang-drink}
|
||||||
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456}
|
||||||
|
volumes:
|
||||||
|
- mysql:/var/lib/mysql/
|
||||||
|
- ./sql/mysql/yixiang-drink.sql:/docker-entrypoint-initdb.d/yixiang-drink.sql:ro
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: yshop-redis
|
||||||
|
image: redis:6-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis:/data
|
||||||
|
|
||||||
|
server:
|
||||||
|
container_name: yshop-server
|
||||||
|
build:
|
||||||
|
context: ./yshop-server/
|
||||||
|
image: yshop-server
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "48080:48080"
|
||||||
|
environment:
|
||||||
|
# https://github.com/polovyivan/docker-pass-configs-to-container
|
||||||
|
SPRING_PROFILES_ACTIVE: local
|
||||||
|
JAVA_OPTS:
|
||||||
|
${JAVA_OPTS:-
|
||||||
|
-Xms512m
|
||||||
|
-Xmx512m
|
||||||
|
-Djava.security.egd=file:/dev/./urandom
|
||||||
|
}
|
||||||
|
ARGS:
|
||||||
|
--spring.datasource.dynamic.datasource.master.url=${MASTER_DATASOURCE_URL:-jdbc:mysql://yshop-mysql:3306/yixiang-drink?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true}
|
||||||
|
--spring.datasource.dynamic.datasource.master.username=${MASTER_DATASOURCE_USERNAME:-root}
|
||||||
|
--spring.datasource.dynamic.datasource.master.password=${MASTER_DATASOURCE_PASSWORD:-123456}
|
||||||
|
--spring.datasource.dynamic.datasource.slave.url=${SLAVE_DATASOURCE_URL:-jdbc:mysql://yshop-mysql:3306/yixiang-drink?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true}
|
||||||
|
--spring.datasource.dynamic.datasource.slave.username=${SLAVE_DATASOURCE_USERNAME:-root}
|
||||||
|
--spring.datasource.dynamic.datasource.slave.password=${SLAVE_DATASOURCE_PASSWORD:-123456}
|
||||||
|
--spring.data.redis.host=${REDIS_HOST:-yshop-redis}
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- redis
|
||||||
|
|
||||||
|
admin:
|
||||||
|
container_name: yshop-admin
|
||||||
|
build:
|
||||||
|
context: ./yshop-ui-admin
|
||||||
|
args:
|
||||||
|
NODE_ENV:
|
||||||
|
ENV=${NODE_ENV:-production}
|
||||||
|
PUBLIC_PATH=${PUBLIC_PATH:-/}
|
||||||
|
VUE_APP_TITLE=${VUE_APP_TITLE:-意象商城管理系统}
|
||||||
|
VUE_APP_BASE_API=${VUE_APP_BASE_API:-/prod-api}
|
||||||
|
VUE_APP_APP_NAME=${VUE_APP_APP_NAME:-/}
|
||||||
|
VUE_APP_TENANT_ENABLE=${VUE_APP_TENANT_ENABLE:-true}
|
||||||
|
VUE_APP_CAPTCHA_ENABLE=${VUE_APP_CAPTCHA_ENABLE:-true}
|
||||||
|
VUE_APP_DOC_ENABLE=${VUE_APP_DOC_ENABLE:-true}
|
||||||
|
VUE_APP_BAIDU_CODE=${VUE_APP_BAIDU_CODE:-fadc1bd5db1a1d6f581df60a1807f8ab}
|
||||||
|
image: yshop-admin
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql:
|
||||||
|
driver: local
|
||||||
|
redis:
|
||||||
|
driver: local
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
## mysql
|
||||||
|
MYSQL_DATABASE=yixiang-drink
|
||||||
|
MYSQL_ROOT_PASSWORD=123456
|
||||||
|
|
||||||
|
## server
|
||||||
|
JAVA_OPTS=-Xms512m -Xmx512m -Djava.security.egd=file:/dev/./urandom
|
||||||
|
|
||||||
|
MASTER_DATASOURCE_URL=jdbc:mysql://yshop-mysql:3306/${MYSQL_DATABASE}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||||
|
MASTER_DATASOURCE_USERNAME=root
|
||||||
|
MASTER_DATASOURCE_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||||
|
SLAVE_DATASOURCE_URL=${MASTER_DATASOURCE_URL}
|
||||||
|
SLAVE_DATASOURCE_USERNAME=${MASTER_DATASOURCE_USERNAME}
|
||||||
|
SLAVE_DATASOURCE_PASSWORD=${MASTER_DATASOURCE_PASSWORD}
|
||||||
|
REDIS_HOST=yshop-redis
|
||||||
|
|
||||||
|
## admin
|
||||||
|
NODE_ENV=production
|
||||||
|
PUBLIC_PATH=/
|
||||||
|
VUE_APP_TITLE=意象商城管理系统
|
||||||
|
VUE_APP_BASE_API=/prod-api
|
||||||
|
VUE_APP_APP_NAME=/
|
||||||
|
VUE_APP_TENANT_ENABLE=true
|
||||||
|
VUE_APP_CAPTCHA_ENABLE=true
|
||||||
|
VUE_APP_DOC_ENABLE=true
|
||||||
|
VUE_APP_BAIDU_CODE=fadc1bd5db1a1d6f581df60a1807f8ab
|
||||||
@@ -3,7 +3,7 @@ set -e
|
|||||||
|
|
||||||
DATE=$(date +%Y%m%d%H%M)
|
DATE=$(date +%Y%m%d%H%M)
|
||||||
# 基础路径
|
# 基础路径
|
||||||
BASE_PATH=/work/projects/yshop-pro-server
|
BASE_PATH=/work/projects/yshop-server
|
||||||
# 编译后 jar 的地址。部署时,Jenkins 会上传 jar 包到该目录下
|
# 编译后 jar 的地址。部署时,Jenkins 会上传 jar 包到该目录下
|
||||||
SOURCE_PATH=$BASE_PATH/build
|
SOURCE_PATH=$BASE_PATH/build
|
||||||
# 服务名称。同时约定部署服务的 jar 包名字也为它。
|
# 服务名称。同时约定部署服务的 jar 包名字也为它。
|
||||||
@@ -11,13 +11,19 @@ SERVER_NAME=yshop-server
|
|||||||
# 环境
|
# 环境
|
||||||
PROFILES_ACTIVE=development
|
PROFILES_ACTIVE=development
|
||||||
# 健康检查 URL
|
# 健康检查 URL
|
||||||
HEALTH_CHECK_URL=http://127.0.0.1:48081/actuator/health/
|
HEALTH_CHECK_URL=http://127.0.0.1:48080/actuator/health/
|
||||||
|
|
||||||
# heapError 存放路径
|
# heapError 存放路径
|
||||||
HEAP_ERROR_PATH=$BASE_PATH/heapError
|
HEAP_ERROR_PATH=$BASE_PATH/heapError
|
||||||
# JVM 参数
|
# JVM 参数
|
||||||
JAVA_OPS="-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$HEAP_ERROR_PATH"
|
JAVA_OPS="-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$HEAP_ERROR_PATH"
|
||||||
|
|
||||||
|
# SkyWalking Agent 配置
|
||||||
|
#export SW_AGENT_NAME=$SERVER_NAME
|
||||||
|
#export SW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.0.84:11800
|
||||||
|
#export SW_GRPC_LOG_SERVER_HOST=192.168.0.84
|
||||||
|
#export SW_AGENT_TRACE_IGNORE_PATH="Redisson/PING,/actuator/**,/admin/**"
|
||||||
|
#export JAVA_AGENT=-javaagent:/work/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar
|
||||||
|
|
||||||
# 备份
|
# 备份
|
||||||
function backup() {
|
function backup() {
|
||||||
@@ -68,7 +74,7 @@ function stop() {
|
|||||||
if [ -n "$PID" ]; then
|
if [ -n "$PID" ]; then
|
||||||
echo -e ".\c"
|
echo -e ".\c"
|
||||||
else
|
else
|
||||||
echo '[stop] 停止 $BASE_PATH/$SERVER_NAME 成功'
|
echo "[stop] 停止 $BASE_PATH/$SERVER_NAME 成功"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,680 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-dependencies</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>基础 bom 文件,管理整个项目的依赖版本</description>
|
||||||
|
<url>https://gitee.com/guchengwuyue/yshop-drink</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<revision>3.0.0</revision>
|
||||||
|
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
|
||||||
|
<!-- 统一依赖管理 -->
|
||||||
|
<spring.boot.version>3.2.2</spring.boot.version>
|
||||||
|
<!-- Web 相关 -->
|
||||||
|
<springdoc.version>2.2.0</springdoc.version>
|
||||||
|
<knife4j.version>4.3.0</knife4j.version>
|
||||||
|
<!-- DB 相关 -->
|
||||||
|
<druid.version>1.2.21</druid.version>
|
||||||
|
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||||
|
<mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version>
|
||||||
|
<dynamic-datasource.version>4.3.0</dynamic-datasource.version>
|
||||||
|
<mybatis-plus-join.version>1.4.10</mybatis-plus-join.version>
|
||||||
|
<easy-trans.version>2.2.11</easy-trans.version>
|
||||||
|
<redisson.version>3.26.0</redisson.version>
|
||||||
|
<dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
|
||||||
|
<!-- 消息队列 -->
|
||||||
|
<rocketmq-spring.version>2.3.0</rocketmq-spring.version>
|
||||||
|
<!-- 服务保障相关 -->
|
||||||
|
<lock4j.version>2.2.7</lock4j.version>
|
||||||
|
<!-- 监控相关 -->
|
||||||
|
<skywalking.version>9.0.0</skywalking.version>
|
||||||
|
<spring-boot-admin.version>3.2.1</spring-boot-admin.version>
|
||||||
|
<opentracing.version>0.33.0</opentracing.version>
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<podam.version>8.0.1.RELEASE</podam.version>
|
||||||
|
<jedis-mock.version>1.0.13</jedis-mock.version>
|
||||||
|
<mockito-inline.version>5.2.0</mockito-inline.version>
|
||||||
|
<!-- Bpm 工作流相关 -->
|
||||||
|
<flowable.version>7.0.1</flowable.version>
|
||||||
|
<!-- 工具类相关 -->
|
||||||
|
<captcha-plus.version>2.0.3</captcha-plus.version>
|
||||||
|
<jsoup.version>1.17.2</jsoup.version>
|
||||||
|
<lombok.version>1.18.30</lombok.version>
|
||||||
|
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||||
|
<hutool-5.version>5.8.25</hutool-5.version>
|
||||||
|
<hutool-6.version>6.0.0-M10</hutool-6.version>
|
||||||
|
<easyexcel.verion>3.3.3</easyexcel.verion>
|
||||||
|
<velocity.version>2.3</velocity.version>
|
||||||
|
<screw.version>1.0.5</screw.version>
|
||||||
|
<fastjson.version>1.2.83</fastjson.version>
|
||||||
|
<guava.version>33.0.0-jre</guava.version>
|
||||||
|
<guice.version>5.1.0</guice.version>
|
||||||
|
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||||
|
<commons-net.version>3.10.0</commons-net.version>
|
||||||
|
<jsch.version>0.1.55</jsch.version>
|
||||||
|
<tika-core.version>2.9.1</tika-core.version>
|
||||||
|
<ip2region.version>2.7.0</ip2region.version>
|
||||||
|
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
|
||||||
|
<!-- 三方云服务相关 -->
|
||||||
|
<okio.version>3.5.0</okio.version>
|
||||||
|
<okhttp3.version>4.11.0</okhttp3.version>
|
||||||
|
<commons-io.version>2.15.1</commons-io.version>
|
||||||
|
<minio.version>8.5.7</minio.version>
|
||||||
|
<aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version>
|
||||||
|
<aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
|
||||||
|
<tencentcloud-sdk-java.version>3.1.880</tencentcloud-sdk-java.version>
|
||||||
|
<justauth.version>2.0.5</justauth.version>
|
||||||
|
<jimureport.version>1.6.6-beta2</jimureport.version>
|
||||||
|
<xercesImpl.version>2.12.2</xercesImpl.version>
|
||||||
|
<weixin-java.version>4.6.0</weixin-java.version>
|
||||||
|
<pay.boot.version>1.0.3</pay.boot.version>
|
||||||
|
<pay.version>2.14.5</pay.version>
|
||||||
|
<zxing.version>3.3.3</zxing.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<!-- 统一依赖管理 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 业务组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.mouzt</groupId>
|
||||||
|
<artifactId>bizlog-sdk</artifactId>
|
||||||
|
<version>${bizlog-sdk.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion> <!-- 排除掉springboot依赖使用项目的 -->
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-biz-data-permission</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-biz-ip</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring 核心 -->
|
||||||
|
<dependency>
|
||||||
|
<!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Web 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-web</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-security</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-websocket</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
|
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||||
|
<version>${knife4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||||
|
<version>${springdoc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-ui</artifactId>
|
||||||
|
<version>${springdoc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- DB 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-mybatis</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||||
|
<version>${druid.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器,使用它解析表结构 -->
|
||||||
|
<version>${mybatis-plus-generator.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
|
||||||
|
<version>${dynamic-datasource.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.yulichang</groupId>
|
||||||
|
<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
|
||||||
|
<version>${mybatis-plus-join.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||||
|
<artifactId>easy-trans-spring-boot-starter</artifactId>
|
||||||
|
<version>${easy-trans.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-commons</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fhs-opensource</groupId>
|
||||||
|
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||||
|
<version>${easy-trans.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fhs-opensource</groupId>
|
||||||
|
<artifactId>easy-trans-anno</artifactId>
|
||||||
|
<version>${easy-trans.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-redis</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.redisson</groupId>
|
||||||
|
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||||
|
<version>${redisson.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.dameng</groupId>
|
||||||
|
<artifactId>DmJdbcDriver18</artifactId>
|
||||||
|
<version>${dm8.jdbc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Job 定时任务相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-job</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 消息队列相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-mq</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
|
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||||
|
<version>${rocketmq-spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 服务保障相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-protection</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||||
|
<version>${lock4j.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||||
|
<groupId>org.redisson</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 监控相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-monitor</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.skywalking</groupId>
|
||||||
|
<artifactId>apm-toolkit-trace</artifactId>
|
||||||
|
<version>${skywalking.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.skywalking</groupId>
|
||||||
|
<artifactId>apm-toolkit-logback-1.x</artifactId>
|
||||||
|
<version>${skywalking.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.skywalking</groupId>
|
||||||
|
<artifactId>apm-toolkit-opentracing</artifactId>
|
||||||
|
<version>${skywalking.version}</version>
|
||||||
|
<!-- <exclusions>-->
|
||||||
|
<!-- <exclusion>-->
|
||||||
|
<!-- <artifactId>opentracing-api</artifactId>-->
|
||||||
|
<!-- <groupId>io.opentracing</groupId>-->
|
||||||
|
<!-- </exclusion>-->
|
||||||
|
<!-- <exclusion>-->
|
||||||
|
<!-- <artifactId>opentracing-util</artifactId>-->
|
||||||
|
<!-- <groupId>io.opentracing</groupId>-->
|
||||||
|
<!-- </exclusion>-->
|
||||||
|
<!-- </exclusions>-->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.opentracing</groupId>
|
||||||
|
<artifactId>opentracing-api</artifactId>
|
||||||
|
<version>${opentracing.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.opentracing</groupId>
|
||||||
|
<artifactId>opentracing-util</artifactId>
|
||||||
|
<version>${opentracing.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.opentracing</groupId>
|
||||||
|
<artifactId>opentracing-noop</artifactId>
|
||||||
|
<version>${opentracing.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.codecentric</groupId>
|
||||||
|
<artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
|
||||||
|
<version>${spring-boot-admin.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>de.codecentric</groupId>
|
||||||
|
<artifactId>spring-boot-admin-server-cloud</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.codecentric</groupId>
|
||||||
|
<artifactId>spring-boot-admin-starter-client</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
|
||||||
|
<version>${spring-boot-admin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-test</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-inline</artifactId>
|
||||||
|
<version>${mockito-inline.version}</version> <!-- 支持 Mockito 的 final 类与 static 方法的 mock -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>asm</artifactId>
|
||||||
|
<groupId>org.ow2.asm</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.fppt</groupId> <!-- 单元测试,我们采用内嵌的 Redis 数据库 -->
|
||||||
|
<artifactId>jedis-mock</artifactId>
|
||||||
|
<version>${jedis-mock.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>uk.co.jemos.podam</groupId> <!-- 单元测试,随机生成 POJO 类 -->
|
||||||
|
<artifactId>podam</artifactId>
|
||||||
|
<version>${podam.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 工作流相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flowable</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-process</artifactId>
|
||||||
|
<version>${flowable.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flowable</groupId>
|
||||||
|
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
|
||||||
|
<version>${flowable.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 工作流相关结束 -->
|
||||||
|
|
||||||
|
<!-- 工具类相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-common</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-excel</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||||
|
<version>${mapstruct.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-jdk8</artifactId>
|
||||||
|
<version>${mapstruct.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
<version>${mapstruct.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>${hutool-5.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara.hutool</groupId>
|
||||||
|
<artifactId>hutool-extra</artifactId>
|
||||||
|
<version>${hutool-6.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>easyexcel</artifactId>
|
||||||
|
<version>${easyexcel.verion}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>${commons-io.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tika</groupId>
|
||||||
|
<artifactId>tika-core</artifactId> <!-- 文件类型的识别 -->
|
||||||
|
<version>${tika-core.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.velocity</groupId>
|
||||||
|
<artifactId>velocity-engine-core</artifactId>
|
||||||
|
<version>${velocity.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>${fastjson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>${guava.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.inject</groupId>
|
||||||
|
<artifactId>guice</artifactId>
|
||||||
|
<version>${guice.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>transmittable-thread-local</artifactId> <!-- 解决 ThreadLocal 父子线程的传值问题 -->
|
||||||
|
<version>${transmittable-thread-local.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-net</groupId>
|
||||||
|
<artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 -->
|
||||||
|
<version>${commons-net.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
|
||||||
|
<version>${jsch.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.xingyuv</groupId>
|
||||||
|
<artifactId>spring-boot-starter-captcha-plus</artifactId>
|
||||||
|
<version>${captcha-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.lionsoul</groupId>
|
||||||
|
<artifactId>ip2region</artifactId>
|
||||||
|
<version>${ip2region.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>${jsoup.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 三方云服务相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okio</groupId>
|
||||||
|
<artifactId>okio</artifactId>
|
||||||
|
<version>${okio.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>${okhttp3.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.minio</groupId>
|
||||||
|
<artifactId>minio</artifactId>
|
||||||
|
<version>${minio.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SMS SDK begin -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||||
|
<version>${aliyun-java-sdk-core.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>opentracing-api</artifactId>
|
||||||
|
<groupId>io.opentracing</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>opentracing-util</artifactId>
|
||||||
|
<groupId>io.opentracing</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
|
||||||
|
<version>${aliyun-java-sdk-dysmsapi.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.tencentcloudapi</groupId>
|
||||||
|
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||||
|
<version>${tencentcloud-sdk-java.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- SMS SDK end -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.xingyuv</groupId>
|
||||||
|
<artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
|
||||||
|
<version>${justauth.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-core</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>weixin-java-pay</artifactId>
|
||||||
|
<version>${weixin-java.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
|
||||||
|
<version>${weixin-java.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
|
||||||
|
<version>${weixin-java.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 积木报表-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeecgframework.jimureport</groupId>
|
||||||
|
<artifactId>jimureport-spring-boot3-starter</artifactId>
|
||||||
|
<version>${jimureport.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xerces</groupId>
|
||||||
|
<artifactId>xercesImpl</artifactId>
|
||||||
|
<version>${xercesImpl.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- pay -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.egzosn</groupId>
|
||||||
|
<artifactId>pay-spring-boot-starter</artifactId>
|
||||||
|
<version>${pay.boot.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.egzosn</groupId>
|
||||||
|
<artifactId>pay-java-ali</artifactId>
|
||||||
|
<version>${pay.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.egzosn</groupId>
|
||||||
|
<artifactId>pay-java-wx</artifactId>
|
||||||
|
<version>${pay.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.egzosn</groupId>
|
||||||
|
<artifactId>pay-java-web-support</artifactId>
|
||||||
|
<version>${pay.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>core</artifactId>
|
||||||
|
<version>${zxing.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- 统一 revision 版本 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>flatten-maven-plugin</artifactId>
|
||||||
|
<version>${flatten-maven-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||||||
|
<updatePomFile>true</updatePomFile>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>flatten</goal>
|
||||||
|
</goals>
|
||||||
|
<id>flatten</id>
|
||||||
|
<phase>process-resources</phase>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>clean</goal>
|
||||||
|
</goals>
|
||||||
|
<id>flatten.clean</id>
|
||||||
|
<phase>clean</phase>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>yshop</artifactId>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<modules>
|
||||||
|
<module>yshop-common</module>
|
||||||
|
<module>yshop-spring-boot-starter-mybatis</module>
|
||||||
|
<module>yshop-spring-boot-starter-redis</module>
|
||||||
|
<module>yshop-spring-boot-starter-web</module>
|
||||||
|
<module>yshop-spring-boot-starter-security</module>
|
||||||
|
<module>yshop-spring-boot-starter-websocket</module>
|
||||||
|
|
||||||
|
<module>yshop-spring-boot-starter-monitor</module>
|
||||||
|
<module>yshop-spring-boot-starter-protection</module>
|
||||||
|
<module>yshop-spring-boot-starter-job</module>
|
||||||
|
<module>yshop-spring-boot-starter-mq</module>
|
||||||
|
|
||||||
|
<module>yshop-spring-boot-starter-excel</module>
|
||||||
|
<module>yshop-spring-boot-starter-test</module>
|
||||||
|
|
||||||
|
<module>yshop-spring-boot-starter-biz-tenant</module>
|
||||||
|
<module>yshop-spring-boot-starter-biz-data-permission</module>
|
||||||
|
<module>yshop-spring-boot-starter-biz-ip</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<artifactId>yshop-framework</artifactId>
|
||||||
|
<description>
|
||||||
|
该包是技术组件,每个子包,代表一个组件。每个组件包括两部分:
|
||||||
|
1. core 包:是该组件的核心封装
|
||||||
|
2. config 包:是该组件基于 Spring 的配置
|
||||||
|
|
||||||
|
技术组件,也分成两类:
|
||||||
|
1. 框架组件:和我们熟悉的 MyBatis、Redis 等等的拓展
|
||||||
|
2. 业务组件:和业务相关的组件的封装,例如说数据字典、操作日志等等。
|
||||||
|
如果是业务组件,Maven 名字会包含 biz
|
||||||
|
</description>
|
||||||
|
<url>https://gitee.com/guchengwuyue/yshop-drink</url>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-framework</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>yshop-common</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>定义基础 pojo 类、枚举、工具类等等</description>
|
||||||
|
<url>https://gitee.com/guchengwuyue/yshop-drink</url>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring 核心 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-core</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-expression</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-aop</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjweaver</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Web 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-web</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 监控相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.skywalking</groupId>
|
||||||
|
<artifactId>apm-toolkit-trace</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 工具类相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||||
|
<artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
+3
@@ -184,4 +184,7 @@ public interface ShopConstants {
|
|||||||
|
|
||||||
//快递查询接口Logistic
|
//快递查询接口Logistic
|
||||||
String KDNIAO_LOGISTIC_QUERY="https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx";
|
String KDNIAO_LOGISTIC_QUERY="https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx";
|
||||||
|
|
||||||
|
//跳转到扫码页面
|
||||||
|
String PAGE_GOOD_HOME = "pages/components/pages/scan/scan";
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
+3
-1
@@ -4,6 +4,8 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key Value 的键值对
|
* Key Value 的键值对
|
||||||
*
|
*
|
||||||
@@ -12,7 +14,7 @@ import lombok.NoArgsConstructor;
|
|||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class KeyValue<K, V> {
|
public class KeyValue<K, V> implements Serializable {
|
||||||
|
|
||||||
private K key;
|
private K key;
|
||||||
private V value;
|
private V value;
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
|
import co.yixiang.yshop.framework.common.core.IntArrayValuable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用状态枚举
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum CommonStatusEnum implements IntArrayValuable {
|
||||||
|
|
||||||
|
ENABLE(0, "开启"),
|
||||||
|
DISABLE(1, "关闭");
|
||||||
|
|
||||||
|
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态值
|
||||||
|
*/
|
||||||
|
private final Integer status;
|
||||||
|
/**
|
||||||
|
* 状态名
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEnable(Integer status) {
|
||||||
|
return ObjUtil.equal(ENABLE.status, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDisable(Integer status) {
|
||||||
|
return ObjUtil.equal(DISABLE.status, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import co.yixiang.yshop.framework.common.core.IntArrayValuable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间间隔的枚举
|
||||||
|
*
|
||||||
|
* @author dhb52
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum DateIntervalEnum implements IntArrayValuable {
|
||||||
|
|
||||||
|
DAY(1, "天"),
|
||||||
|
WEEK(2, "周"),
|
||||||
|
MONTH(3, "月"),
|
||||||
|
QUARTER(4, "季度"),
|
||||||
|
YEAR(5, "年")
|
||||||
|
;
|
||||||
|
|
||||||
|
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getInterval).toArray();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
private final Integer interval;
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateIntervalEnum valueOf(Integer interval) {
|
||||||
|
return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -12,7 +12,7 @@ import lombok.Getter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum DocumentEnum {
|
public enum DocumentEnum {
|
||||||
|
|
||||||
REDIS_INSTALL("https://www.yixiang.coJ", "Redis 安装文档"),
|
REDIS_INSTALL("https://gitee.com/zhijiantianya/yixiang-drink/issues/I4VCSJ", "Redis 安装文档"),
|
||||||
TENANT("https://www.yixiang.co", "SaaS 多租户文档");
|
TENANT("https://www.yixiang.co", "SaaS 多租户文档");
|
||||||
|
|
||||||
private final String url;
|
private final String url;
|
||||||
+3
@@ -52,6 +52,9 @@ public enum OrderInfoEnum {
|
|||||||
PAY_CHANNEL_0(0,"公众号/H5支付渠道"),
|
PAY_CHANNEL_0(0,"公众号/H5支付渠道"),
|
||||||
PAY_CHANNEL_1(1,"小程序支付渠道"),
|
PAY_CHANNEL_1(1,"小程序支付渠道"),
|
||||||
|
|
||||||
|
DESK_ORDER_STATUS_CONFIRM(0,"确认完成"),
|
||||||
|
DESK_ORDER_STATUS_ING(1,"就餐中"),
|
||||||
|
|
||||||
|
|
||||||
SHIPPIING_TYPE_1(1,"快递"),
|
SHIPPIING_TYPE_1(1,"快递"),
|
||||||
SHIPPIING_TYPE_2(2,"门店自提");
|
SHIPPIING_TYPE_2(2,"门店自提");
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.enums;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hupeng
|
||||||
|
* 类型枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum OrderTypeEnum {
|
||||||
|
TYPE_WORK("work","工作台"),
|
||||||
|
TYPE_COMMON("common","普通");
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private String desc;
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.enums;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hupeng
|
||||||
|
* 支付ID枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum PayIdEnum {
|
||||||
|
WX_H5("wx_h5","微信支付H5"),
|
||||||
|
WX_MINIAPP("wx_miniapp","微信支付小程序"),
|
||||||
|
WX_WECHAT("wx_wechat","微信支付公众号"),
|
||||||
|
WX_PC("wx_pc","微信支付pc"),
|
||||||
|
WX_APP("wx_app","微信支付app"),
|
||||||
|
ALI_H5("ali_h5","支付宝H5"),
|
||||||
|
ALI_MINIAPP("ali_miniapp","支付宝小程序"),
|
||||||
|
ALI_WECHAT("ali_wechat","支付宝公众号"),
|
||||||
|
ALI_PC("ali_pc","支付宝pc"),
|
||||||
|
ALI_APP("ali_app","支付宝app");
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private String desc;
|
||||||
|
}
|
||||||
File renamed without changes.
+2
-2
@@ -15,11 +15,11 @@ import java.util.Arrays;
|
|||||||
@Getter
|
@Getter
|
||||||
public enum TerminalEnum implements IntArrayValuable {
|
public enum TerminalEnum implements IntArrayValuable {
|
||||||
|
|
||||||
|
UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它
|
||||||
WECHAT_MINI_PROGRAM(10, "微信小程序"),
|
WECHAT_MINI_PROGRAM(10, "微信小程序"),
|
||||||
WECHAT_WAP(11, "微信公众号"),
|
WECHAT_WAP(11, "微信公众号"),
|
||||||
H5(20, "H5 网页"),
|
H5(20, "H5 网页"),
|
||||||
IOS(31, "苹果 App"),
|
APP(31, "手机 App"),
|
||||||
ANDROID(32, "安卓 App"),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();
|
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();
|
||||||
File renamed without changes.
-2
@@ -27,8 +27,6 @@ public interface WebFilterOrderEnum {
|
|||||||
|
|
||||||
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面
|
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面
|
||||||
|
|
||||||
int ACTIVITI_FILTER = -98; // 需要保证在 Spring Security 过滤后面
|
|
||||||
|
|
||||||
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
|
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
|
||||||
|
|
||||||
int DEMO_FILTER = Integer.MAX_VALUE;
|
int DEMO_FILTER = Integer.MAX_VALUE;
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-11
@@ -30,6 +30,7 @@ public interface GlobalErrorCodeConstants {
|
|||||||
|
|
||||||
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
|
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
|
||||||
ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
|
ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
|
||||||
|
ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项");
|
||||||
|
|
||||||
// ========== 自定义错误段 ==========
|
// ========== 自定义错误段 ==========
|
||||||
ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
|
ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
|
||||||
@@ -37,15 +38,4 @@ public interface GlobalErrorCodeConstants {
|
|||||||
|
|
||||||
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
|
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为服务端错误,参考 HTTP 5XX 错误码段
|
|
||||||
*
|
|
||||||
* @param code 错误码
|
|
||||||
* @return 是否
|
|
||||||
*/
|
|
||||||
static boolean isServerErrorCode(Integer code) {
|
|
||||||
return code != null
|
|
||||||
&& code >= INTERNAL_SERVER_ERROR.getCode() && code <= INTERNAL_SERVER_ERROR.getCode() + 99;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+7
@@ -33,7 +33,14 @@ public class ServiceErrorCodeRange {
|
|||||||
// 模块 system 错误码区间 [1-002-000-000 ~ 1-003-000-000)
|
// 模块 system 错误码区间 [1-002-000-000 ~ 1-003-000-000)
|
||||||
// 模块 report 错误码区间 [1-003-000-000 ~ 1-004-000-000)
|
// 模块 report 错误码区间 [1-003-000-000 ~ 1-004-000-000)
|
||||||
// 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)
|
// 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)
|
||||||
|
// 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000)
|
||||||
// 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)
|
// 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)
|
||||||
// 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)
|
// 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)
|
||||||
|
|
||||||
|
// 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)
|
||||||
|
// 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000)
|
||||||
|
// 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000)
|
||||||
|
|
||||||
|
// 模块 crm 错误码区间 [1-020-000-000 ~ 1-021-000-000)
|
||||||
|
|
||||||
}
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.exception.util;
|
||||||
|
|
||||||
|
import co.yixiang.yshop.framework.common.exception.ErrorCode;
|
||||||
|
import co.yixiang.yshop.framework.common.exception.ServiceException;
|
||||||
|
import co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ServiceException} 工具类
|
||||||
|
*
|
||||||
|
* 目的在于,格式化异常信息提示。
|
||||||
|
* 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ServiceExceptionUtil {
|
||||||
|
|
||||||
|
// ========== 和 ServiceException 的集成 ==========
|
||||||
|
|
||||||
|
public static ServiceException exception(ErrorCode errorCode) {
|
||||||
|
return exception0(errorCode.getCode(), errorCode.getMsg());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServiceException exception(ErrorCode errorCode, Object... params) {
|
||||||
|
return exception0(errorCode.getCode(), errorCode.getMsg(), params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
|
||||||
|
String message = doFormat(code, messagePattern, params);
|
||||||
|
return new ServiceException(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServiceException invalidParamException(String messagePattern, Object... params) {
|
||||||
|
return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 格式化方法 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将错误编号对应的消息使用 params 进行格式化。
|
||||||
|
*
|
||||||
|
* @param code 错误编号
|
||||||
|
* @param messagePattern 消息模版
|
||||||
|
* @param params 参数
|
||||||
|
* @return 格式化后的提示
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static String doFormat(int code, String messagePattern, Object... params) {
|
||||||
|
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
|
||||||
|
int i = 0;
|
||||||
|
int j;
|
||||||
|
int l;
|
||||||
|
for (l = 0; l < params.length; l++) {
|
||||||
|
j = messagePattern.indexOf("{}", i);
|
||||||
|
if (j == -1) {
|
||||||
|
log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
|
||||||
|
if (i == 0) {
|
||||||
|
return messagePattern;
|
||||||
|
} else {
|
||||||
|
sbuf.append(messagePattern.substring(i));
|
||||||
|
return sbuf.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sbuf.append(messagePattern, i, j);
|
||||||
|
sbuf.append(params[l]);
|
||||||
|
i = j + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messagePattern.indexOf("{}", i) != -1) {
|
||||||
|
log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
|
||||||
|
}
|
||||||
|
sbuf.append(messagePattern.substring(i));
|
||||||
|
return sbuf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File renamed without changes.
-5
@@ -1,7 +1,6 @@
|
|||||||
package co.yixiang.yshop.framework.common.pojo;
|
package co.yixiang.yshop.framework.common.pojo;
|
||||||
|
|
||||||
import co.yixiang.yshop.framework.common.exception.ErrorCode;
|
import co.yixiang.yshop.framework.common.exception.ErrorCode;
|
||||||
import co.yixiang.yshop.framework.common.exception.ServerException;
|
|
||||||
import co.yixiang.yshop.framework.common.exception.ServiceException;
|
import co.yixiang.yshop.framework.common.exception.ServiceException;
|
||||||
import co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;
|
import co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
@@ -92,10 +91,6 @@ public class CommonResult<T> implements Serializable {
|
|||||||
if (isSuccess()) {
|
if (isSuccess()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 服务端异常
|
|
||||||
if (GlobalErrorCodeConstants.isServerErrorCode(code)) {
|
|
||||||
throw new ServerException(code, msg);
|
|
||||||
}
|
|
||||||
// 业务异常
|
// 业务异常
|
||||||
throw new ServiceException(code, msg);
|
throw new ServiceException(code, msg);
|
||||||
}
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.pojo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.Max;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Schema(description="分页参数")
|
||||||
|
@Data
|
||||||
|
public class PageParam implements Serializable {
|
||||||
|
|
||||||
|
private static final Integer PAGE_NO = 1;
|
||||||
|
private static final Integer PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每页条数 - 不分页
|
||||||
|
*
|
||||||
|
* 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。
|
||||||
|
*/
|
||||||
|
public static final Integer PAGE_SIZE_NONE = -1;
|
||||||
|
|
||||||
|
@Schema(description = "页码,从 1 开始", requiredMode = Schema.RequiredMode.REQUIRED,example = "1")
|
||||||
|
@NotNull(message = "页码不能为空")
|
||||||
|
@Min(value = 1, message = "页码最小值为 1")
|
||||||
|
private Integer pageNo = PAGE_NO;
|
||||||
|
|
||||||
|
@Schema(description = "每页条数,最大值为 100", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||||
|
@NotNull(message = "每页条数不能为空")
|
||||||
|
@Min(value = 1, message = "每页条数最小值为 1")
|
||||||
|
@Max(value = 100, message = "每页条数最大值为 100")
|
||||||
|
private Integer pageSize = PAGE_SIZE;
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -11,10 +11,10 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
public final class PageResult<T> implements Serializable {
|
public final class PageResult<T> implements Serializable {
|
||||||
|
|
||||||
@Schema(description = "数据", required = true)
|
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private List<T> list;
|
private List<T> list;
|
||||||
|
|
||||||
@Schema(description = "总量", required = true)
|
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private Long total;
|
private Long total;
|
||||||
|
|
||||||
public PageResult() {
|
public PageResult() {
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.pojo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "可排序的分页参数")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class SortablePageParam extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "排序字段")
|
||||||
|
private List<SortingField> sortingFields;
|
||||||
|
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序字段 DTO
|
||||||
|
*
|
||||||
|
* 类名加了 ing 的原因是,避免和 ES SortField 重名。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SortingField implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顺序 - 升序
|
||||||
|
*/
|
||||||
|
public static final String ORDER_ASC = "asc";
|
||||||
|
/**
|
||||||
|
* 顺序 - 降序
|
||||||
|
*/
|
||||||
|
public static final String ORDER_DESC = "desc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段
|
||||||
|
*/
|
||||||
|
private String field;
|
||||||
|
/**
|
||||||
|
* 顺序
|
||||||
|
*/
|
||||||
|
private String order;
|
||||||
|
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.cache;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache 工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class CacheUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建异步刷新的 LoadingCache 对象
|
||||||
|
*
|
||||||
|
* 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法
|
||||||
|
*
|
||||||
|
* 或者简单理解:
|
||||||
|
* 1、和“人”相关的,使用 {@link #buildCache(Duration, CacheLoader)} 方法
|
||||||
|
* 2、和“全局”、“系统”相关的,使用当前缓存方法
|
||||||
|
*
|
||||||
|
* @param duration 过期时间
|
||||||
|
* @param loader CacheLoader 对象
|
||||||
|
* @return LoadingCache 对象
|
||||||
|
*/
|
||||||
|
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
|
||||||
|
return CacheBuilder.newBuilder()
|
||||||
|
// 只阻塞当前数据加载线程,其他线程返回旧值
|
||||||
|
.refreshAfterWrite(duration)
|
||||||
|
// 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
|
||||||
|
.build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO yshop:可能要思考下,未来要不要做成可配置
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建同步刷新的 LoadingCache 对象
|
||||||
|
*
|
||||||
|
* @param duration 过期时间
|
||||||
|
* @param loader CacheLoader 对象
|
||||||
|
* @return LoadingCache 对象
|
||||||
|
*/
|
||||||
|
public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
|
||||||
|
return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File renamed without changes.
+322
@@ -0,0 +1,322 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.collection;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection 工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class CollectionUtils {
|
||||||
|
|
||||||
|
public static boolean containsAny(Object source, Object... targets) {
|
||||||
|
return asList(targets).contains(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAnyEmpty(Collection<?>... collections) {
|
||||||
|
return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
|
||||||
|
return from.stream().anyMatch(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return from.stream().filter(predicate).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return distinct(from, keyMapper, (t1, t2) -> t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
|
||||||
|
if (ArrayUtil.isEmpty(from)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return convertList(Arrays.asList(from), func);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
|
||||||
|
Function<T, ? extends Stream<? extends U>> func) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
|
||||||
|
Function<? super T, ? extends U> mapper,
|
||||||
|
Function<U, ? extends Stream<? extends R>> func) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
|
||||||
|
return map.values()
|
||||||
|
.stream()
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Set<T> convertSet(Collection<T> from) {
|
||||||
|
return convertSet(from, v -> v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
|
||||||
|
Function<T, ? extends Stream<? extends U>> func) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
|
||||||
|
Function<? super T, ? extends U> mapper,
|
||||||
|
Function<U, ? extends Stream<? extends R>> func) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return convertMap(from, keyFunc, Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc, Supplier<? extends Map<K, T>> supplier) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
return convertMap(from, keyFunc, Function.identity(), supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, Supplier<? extends Map<K, V>> supplier) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return from.stream()
|
||||||
|
.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂时没想好名字,先以 2 结尾噶
|
||||||
|
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();
|
||||||
|
from.forEach(item -> builder.put(keyFunc.apply(item), item));
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对比老、新两个列表,找出新增、修改、删除的数据
|
||||||
|
*
|
||||||
|
* @param oldList 老列表
|
||||||
|
* @param newList 新列表
|
||||||
|
* @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
|
||||||
|
* 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
|
||||||
|
* @return [新增列表、修改列表、删除列表]
|
||||||
|
*/
|
||||||
|
public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
|
||||||
|
BiFunction<T, T, Boolean> sameFunc) {
|
||||||
|
List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
|
||||||
|
List<T> updateList = new ArrayList<>();
|
||||||
|
List<T> deleteList = new ArrayList<>();
|
||||||
|
|
||||||
|
// 通过以 oldList 为主遍历,找出 updateList 和 deleteList
|
||||||
|
for (T oldObj : oldList) {
|
||||||
|
// 1. 寻找是否有匹配的
|
||||||
|
T foundObj = null;
|
||||||
|
for (Iterator<T> iterator = createList.iterator(); iterator.hasNext(); ) {
|
||||||
|
T newObj = iterator.next();
|
||||||
|
// 1.1 不匹配,则直接跳过
|
||||||
|
if (!sameFunc.apply(oldObj, newObj)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 1.2 匹配,则移除,并结束寻找
|
||||||
|
iterator.remove();
|
||||||
|
foundObj = newObj;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
|
||||||
|
if (foundObj != null) {
|
||||||
|
updateList.add(foundObj);
|
||||||
|
} else {
|
||||||
|
deleteList.add(oldObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asList(createList, updateList, deleteList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
|
||||||
|
return org.springframework.util.CollectionUtils.containsAny(source, candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T getFirst(List<T> from) {
|
||||||
|
return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) {
|
||||||
|
return findFirst(from, predicate, Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return from.stream().filter(predicate).findFirst().map(func).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
assert !from.isEmpty(); // 断言,避免告警
|
||||||
|
T t = from.stream().max(Comparator.comparing(valueFunc)).get();
|
||||||
|
return valueFunc.apply(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
assert from.size() > 0; // 断言,避免告警
|
||||||
|
T t = from.stream().min(Comparator.comparing(valueFunc)).get();
|
||||||
|
return valueFunc.apply(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
|
||||||
|
BinaryOperator<V> accumulator) {
|
||||||
|
return getSumValue(from, valueFunc, accumulator, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
|
||||||
|
BinaryOperator<V> accumulator, V defaultValue) {
|
||||||
|
if (CollUtil.isEmpty(from)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
assert !from.isEmpty(); // 断言,避免告警
|
||||||
|
return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void addIfNotNull(Collection<T> coll, T item) {
|
||||||
|
if (item == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
coll.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Collection<T> singleton(T obj) {
|
||||||
|
return obj == null ? Collections.emptyList() : Collections.singleton(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> newArrayList(List<List<T>> list) {
|
||||||
|
return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+3
-1
@@ -2,6 +2,7 @@ package co.yixiang.yshop.framework.common.util.collection;
|
|||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import co.yixiang.yshop.framework.common.core.KeyValue;
|
import co.yixiang.yshop.framework.common.core.KeyValue;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
@@ -40,6 +41,7 @@ public class MapUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 从哈希表查找到 key 对应的 value,然后进一步处理
|
* 从哈希表查找到 key 对应的 value,然后进一步处理
|
||||||
|
* key 为 null 时, 不处理
|
||||||
* 注意,如果查找到的 value 为 null 时,不进行处理
|
* 注意,如果查找到的 value 为 null 时,不进行处理
|
||||||
*
|
*
|
||||||
* @param map 哈希表
|
* @param map 哈希表
|
||||||
@@ -47,7 +49,7 @@ public class MapUtils {
|
|||||||
* @param consumer 进一步处理的逻辑
|
* @param consumer 进一步处理的逻辑
|
||||||
*/
|
*/
|
||||||
public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
|
public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
|
||||||
if (CollUtil.isEmpty(map)) {
|
if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
V value = map.get(key);
|
V value = map.get(key);
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.collection;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set 工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class SetUtils {
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> Set<T> asSet(T... objs) {
|
||||||
|
return CollUtil.newHashSet(objs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+18
-42
@@ -23,9 +23,9 @@ public class DateUtils {
|
|||||||
*/
|
*/
|
||||||
public static final long SECOND_MILLIS = 1000;
|
public static final long SECOND_MILLIS = 1000;
|
||||||
|
|
||||||
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
|
public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
|
||||||
|
|
||||||
public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";
|
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 LocalDateTime 转换成 Date
|
* 将 LocalDateTime 转换成 Date
|
||||||
@@ -34,6 +34,9 @@ public class DateUtils {
|
|||||||
* @return LocalDateTime
|
* @return LocalDateTime
|
||||||
*/
|
*/
|
||||||
public static Date of(LocalDateTime date) {
|
public static Date of(LocalDateTime date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
// 将此日期时间与时区相结合以创建 ZonedDateTime
|
// 将此日期时间与时区相结合以创建 ZonedDateTime
|
||||||
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
|
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
|
||||||
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
|
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
|
||||||
@@ -49,30 +52,24 @@ public class DateUtils {
|
|||||||
* @return LocalDateTime
|
* @return LocalDateTime
|
||||||
*/
|
*/
|
||||||
public static LocalDateTime of(Date date) {
|
public static LocalDateTime of(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
// 转为时间戳
|
// 转为时间戳
|
||||||
Instant instant = date.toInstant();
|
Instant instant = date.toInstant();
|
||||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public static Date addTime(Duration duration) {
|
public static Date addTime(Duration duration) {
|
||||||
return new Date(System.currentTimeMillis() + duration.toMillis());
|
return new Date(System.currentTimeMillis() + duration.toMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isExpired(Date time) {
|
|
||||||
return System.currentTimeMillis() > time.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isExpired(LocalDateTime time) {
|
public static boolean isExpired(LocalDateTime time) {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
return now.isAfter(time);
|
return now.isAfter(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long diff(Date endTime, Date startTime) {
|
|
||||||
return endTime.getTime() - startTime.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建指定时间
|
* 创建指定时间
|
||||||
*
|
*
|
||||||
@@ -129,37 +126,6 @@ public class DateUtils {
|
|||||||
return a.isAfter(b) ? a : b;
|
return a.isAfter(b) ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算当期时间相差的日期
|
|
||||||
*
|
|
||||||
* @param field 日历字段.<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY等.
|
|
||||||
* @param amount 相差的数值
|
|
||||||
* @return 计算后的日志
|
|
||||||
*/
|
|
||||||
public static Date addDate(int field, int amount) {
|
|
||||||
return addDate(null, field, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算当期时间相差的日期
|
|
||||||
*
|
|
||||||
* @param date 设置时间
|
|
||||||
* @param field 日历字段 例如说,{@link Calendar#DAY_OF_MONTH} 等
|
|
||||||
* @param amount 相差的数值
|
|
||||||
* @return 计算后的日志
|
|
||||||
*/
|
|
||||||
public static Date addDate(Date date, int field, int amount) {
|
|
||||||
if (amount == 0) {
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
Calendar c = Calendar.getInstance();
|
|
||||||
if (date != null) {
|
|
||||||
c.setTime(date);
|
|
||||||
}
|
|
||||||
c.add(field, amount);
|
|
||||||
return c.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否今天
|
* 是否今天
|
||||||
*
|
*
|
||||||
@@ -170,4 +136,14 @@ public class DateUtils {
|
|||||||
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
|
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否昨天
|
||||||
|
*
|
||||||
|
* @param date 日期
|
||||||
|
* @return 是否
|
||||||
|
*/
|
||||||
|
public static boolean isYesterday(LocalDateTime date) {
|
||||||
|
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+309
@@ -0,0 +1,309 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.date;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import co.yixiang.yshop.framework.common.enums.DateIntervalEnum;
|
||||||
|
|
||||||
|
import java.time.*;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.time.temporal.TemporalAdjusters;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间工具类,用于 {@link java.time.LocalDateTime}
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class LocalDateTimeUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值
|
||||||
|
*/
|
||||||
|
public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析时间
|
||||||
|
*
|
||||||
|
* 相比 {@link LocalDateTimeUtil#parse(CharSequence)} 方法来说,会尽量去解析,直到成功
|
||||||
|
*
|
||||||
|
* @param time 时间
|
||||||
|
* @return 时间字符串
|
||||||
|
*/
|
||||||
|
public static LocalDateTime parse(String time) {
|
||||||
|
try {
|
||||||
|
return LocalDateTimeUtil.parse(time, DatePattern.NORM_DATE_PATTERN);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
return LocalDateTimeUtil.parse(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalDateTime addTime(Duration duration) {
|
||||||
|
return LocalDateTime.now().plus(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalDateTime minusTime(Duration duration) {
|
||||||
|
return LocalDateTime.now().minus(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean beforeNow(LocalDateTime date) {
|
||||||
|
return date.isBefore(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean afterNow(LocalDateTime date) {
|
||||||
|
return date.isAfter(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指定时间
|
||||||
|
*
|
||||||
|
* @param year 年
|
||||||
|
* @param mouth 月
|
||||||
|
* @param day 日
|
||||||
|
* @return 指定时间
|
||||||
|
*/
|
||||||
|
public static LocalDateTime buildTime(int year, int mouth, int day) {
|
||||||
|
return LocalDateTime.of(year, mouth, day, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
|
||||||
|
int year2, int mouth2, int day2) {
|
||||||
|
return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判指定断时间,是否在该时间范围内
|
||||||
|
*
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
* @param time 指定时间
|
||||||
|
* @return 是否
|
||||||
|
*/
|
||||||
|
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, String time) {
|
||||||
|
if (startTime == null || endTime == null || time == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return LocalDateTimeUtil.isIn(parse(time), startTime, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前时间是否在该时间范围内
|
||||||
|
*
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
* @return 是否
|
||||||
|
*/
|
||||||
|
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) {
|
||||||
|
if (startTime == null || endTime == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前时间是否在该时间范围内
|
||||||
|
*
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
* @return 是否
|
||||||
|
*/
|
||||||
|
public static boolean isBetween(String startTime, String endTime) {
|
||||||
|
if (startTime == null || endTime == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LocalDate nowDate = LocalDate.now();
|
||||||
|
return LocalDateTimeUtil.isIn(LocalDateTime.now(),
|
||||||
|
LocalDateTime.of(nowDate, LocalTime.parse(startTime)),
|
||||||
|
LocalDateTime.of(nowDate, LocalTime.parse(endTime)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断时间段是否重叠
|
||||||
|
*
|
||||||
|
* @param startTime1 开始 time1
|
||||||
|
* @param endTime1 结束 time1
|
||||||
|
* @param startTime2 开始 time2
|
||||||
|
* @param endTime2 结束 time2
|
||||||
|
* @return 重叠:true 不重叠:false
|
||||||
|
*/
|
||||||
|
public static boolean isOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
|
||||||
|
LocalDate nowDate = LocalDate.now();
|
||||||
|
return LocalDateTimeUtil.isOverlap(LocalDateTime.of(nowDate, startTime1), LocalDateTime.of(nowDate, endTime1),
|
||||||
|
LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定日期所在的月份的开始时间
|
||||||
|
* 例如:2023-09-30 00:00:00,000
|
||||||
|
*
|
||||||
|
* @param date 日期
|
||||||
|
* @return 月份的开始时间
|
||||||
|
*/
|
||||||
|
public static LocalDateTime beginOfMonth(LocalDateTime date) {
|
||||||
|
return date.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定日期所在的月份的最后时间
|
||||||
|
* 例如:2023-09-30 23:59:59,999
|
||||||
|
*
|
||||||
|
* @param date 日期
|
||||||
|
* @return 月份的结束时间
|
||||||
|
*/
|
||||||
|
public static LocalDateTime endOfMonth(LocalDateTime date) {
|
||||||
|
return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得指定日期所在季度
|
||||||
|
*
|
||||||
|
* @param date 日期
|
||||||
|
* @return 所在季度
|
||||||
|
*/
|
||||||
|
public static int getQuarterOfYear(LocalDateTime date) {
|
||||||
|
return (date.getMonthValue() - 1) / 3 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定日期到现在过了几天,如果指定日期在当前日期之后,获取结果为负
|
||||||
|
*
|
||||||
|
* @param dateTime 日期
|
||||||
|
* @return 相差天数
|
||||||
|
*/
|
||||||
|
public static Long between(LocalDateTime dateTime) {
|
||||||
|
return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取今天的开始时间
|
||||||
|
*
|
||||||
|
* @return 今天
|
||||||
|
*/
|
||||||
|
public static LocalDateTime getToday() {
|
||||||
|
return LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取昨天的开始时间
|
||||||
|
*
|
||||||
|
* @return 昨天
|
||||||
|
*/
|
||||||
|
public static LocalDateTime getYesterday() {
|
||||||
|
return LocalDateTimeUtil.beginOfDay(LocalDateTime.now().minusDays(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取本月的开始时间
|
||||||
|
*
|
||||||
|
* @return 本月
|
||||||
|
*/
|
||||||
|
public static LocalDateTime getMonth() {
|
||||||
|
return beginOfMonth(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取本年的开始时间
|
||||||
|
*
|
||||||
|
* @return 本年
|
||||||
|
*/
|
||||||
|
public static LocalDateTime getYear() {
|
||||||
|
return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime,
|
||||||
|
LocalDateTime endTime,
|
||||||
|
Integer interval) {
|
||||||
|
// 1.1 找到枚举
|
||||||
|
DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
|
||||||
|
Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
|
||||||
|
// 1.2 将时间对齐
|
||||||
|
startTime = LocalDateTimeUtil.beginOfDay(startTime);
|
||||||
|
endTime = LocalDateTimeUtil.endOfDay(endTime);
|
||||||
|
|
||||||
|
// 2. 循环,生成时间范围
|
||||||
|
List<LocalDateTime[]> timeRanges = new ArrayList<>();
|
||||||
|
switch (intervalEnum) {
|
||||||
|
case DAY:
|
||||||
|
while (startTime.isBefore(endTime)) {
|
||||||
|
timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
|
||||||
|
startTime = startTime.plusDays(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WEEK:
|
||||||
|
while (startTime.isBefore(endTime)) {
|
||||||
|
LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);
|
||||||
|
timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});
|
||||||
|
startTime = endOfWeek.plusNanos(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MONTH:
|
||||||
|
while (startTime.isBefore(endTime)) {
|
||||||
|
LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);
|
||||||
|
timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});
|
||||||
|
startTime = endOfMonth.plusNanos(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QUARTER:
|
||||||
|
while (startTime.isBefore(endTime)) {
|
||||||
|
int quarterOfYear = getQuarterOfYear(startTime);
|
||||||
|
LocalDateTime quarterEnd = quarterOfYear == 4
|
||||||
|
? startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1)
|
||||||
|
: startTime.withMonth(quarterOfYear * 3 + 1).withDayOfMonth(1).minusNanos(1);
|
||||||
|
timeRanges.add(new LocalDateTime[]{startTime, quarterEnd});
|
||||||
|
startTime = quarterEnd.plusNanos(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case YEAR:
|
||||||
|
while (startTime.isBefore(endTime)) {
|
||||||
|
LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);
|
||||||
|
timeRanges.add(new LocalDateTime[]{startTime, endOfYear});
|
||||||
|
startTime = endOfYear.plusNanos(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid interval: " + interval);
|
||||||
|
}
|
||||||
|
// 3. 兜底,最后一个时间,需要保持在 endTime 之前
|
||||||
|
LocalDateTime[] lastTimeRange = CollUtil.getLast(timeRanges);
|
||||||
|
if (lastTimeRange != null) {
|
||||||
|
lastTimeRange[1] = endTime;
|
||||||
|
}
|
||||||
|
return timeRanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化时间范围
|
||||||
|
*
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
* @param interval 时间间隔
|
||||||
|
* @return 时间范围
|
||||||
|
*/
|
||||||
|
public static String formatDateRange(LocalDateTime startTime, LocalDateTime endTime, Integer interval) {
|
||||||
|
// 1. 找到枚举
|
||||||
|
DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
|
||||||
|
Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
|
||||||
|
|
||||||
|
// 2. 循环,生成时间范围
|
||||||
|
switch (intervalEnum) {
|
||||||
|
case DAY:
|
||||||
|
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
|
||||||
|
case WEEK:
|
||||||
|
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)
|
||||||
|
+ StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime));
|
||||||
|
case MONTH:
|
||||||
|
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);
|
||||||
|
case QUARTER:
|
||||||
|
return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime));
|
||||||
|
case YEAR:
|
||||||
|
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid interval: " + interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -9,7 +9,7 @@ import org.springframework.util.StringUtils;
|
|||||||
import org.springframework.web.util.UriComponents;
|
import org.springframework.web.util.UriComponents;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
File renamed without changes.
File renamed without changes.
+202
@@ -0,0 +1,202 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.json;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class JsonUtils {
|
||||||
|
|
||||||
|
private static ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
static {
|
||||||
|
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
|
||||||
|
objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 objectMapper 属性
|
||||||
|
* <p>
|
||||||
|
* 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
|
||||||
|
*
|
||||||
|
* @param objectMapper ObjectMapper 对象
|
||||||
|
*/
|
||||||
|
public static void init(ObjectMapper objectMapper) {
|
||||||
|
JsonUtils.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static String toJsonString(Object object) {
|
||||||
|
return objectMapper.writeValueAsString(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static byte[] toJsonByte(Object object) {
|
||||||
|
return objectMapper.writeValueAsBytes(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static String toJsonPrettyString(Object object) {
|
||||||
|
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T parseObject(String text, Class<T> clazz) {
|
||||||
|
if (StrUtil.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(text, clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", text, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T parseObject(String text, String path, Class<T> clazz) {
|
||||||
|
if (StrUtil.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonNode treeNode = objectMapper.readTree(text);
|
||||||
|
JsonNode pathNode = treeNode.path(path);
|
||||||
|
return objectMapper.readValue(pathNode.toString(), clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", text, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T parseObject(String text, Type type) {
|
||||||
|
if (StrUtil.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", text, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字符串解析成指定类型的对象
|
||||||
|
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
|
||||||
|
* 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
|
||||||
|
*
|
||||||
|
* @param text 字符串
|
||||||
|
* @param clazz 类型
|
||||||
|
* @return 对象
|
||||||
|
*/
|
||||||
|
public static <T> T parseObject2(String text, Class<T> clazz) {
|
||||||
|
if (StrUtil.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSONUtil.toBean(text, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
|
||||||
|
if (ArrayUtil.isEmpty(bytes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(bytes, clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", bytes, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(text, typeReference);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", text, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
|
||||||
|
*
|
||||||
|
* @param text 字符串
|
||||||
|
* @param typeReference 类型引用
|
||||||
|
* @return 指定类型的对象
|
||||||
|
*/
|
||||||
|
public static <T> T parseObjectQuietly(String text, TypeReference<T> typeReference) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(text, typeReference);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> parseArray(String text, Class<T> clazz) {
|
||||||
|
if (StrUtil.isEmpty(text)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", text, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> parseArray(String text, String path, Class<T> clazz) {
|
||||||
|
if (StrUtil.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonNode treeNode = objectMapper.readTree(text);
|
||||||
|
JsonNode pathNode = treeNode.path(path);
|
||||||
|
return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", text, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonNode parseTree(String text) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readTree(text);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", text, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonNode parseTree(byte[] text) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readTree(text);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("json parse err,json:{}", text, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isJson(String text) {
|
||||||
|
return JSONUtil.isTypeJSON(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File renamed without changes.
+131
@@ -0,0 +1,131 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.number;
|
||||||
|
|
||||||
|
import cn.hutool.core.math.Money;
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金额工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class MoneyUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金额的小数位数
|
||||||
|
*/
|
||||||
|
private static final int PRICE_SCALE = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 百分比对应的 BigDecimal 对象
|
||||||
|
*/
|
||||||
|
public static final BigDecimal PERCENT_100 = BigDecimal.valueOf(100);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算百分比金额,四舍五入
|
||||||
|
*
|
||||||
|
* @param price 金额
|
||||||
|
* @param rate 百分比,例如说 56.77% 则传入 56.77
|
||||||
|
* @return 百分比金额
|
||||||
|
*/
|
||||||
|
public static Integer calculateRatePrice(Integer price, Double rate) {
|
||||||
|
return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算百分比金额,向下传入
|
||||||
|
*
|
||||||
|
* @param price 金额
|
||||||
|
* @param rate 百分比,例如说 56.77% 则传入 56.77
|
||||||
|
* @return 百分比金额
|
||||||
|
*/
|
||||||
|
public static Integer calculateRatePriceFloor(Integer price, Double rate) {
|
||||||
|
return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算百分比金额
|
||||||
|
*
|
||||||
|
* @param price 金额(单位分)
|
||||||
|
* @param count 数量
|
||||||
|
* @param percent 折扣(单位分),列如 60.2%,则传入 6020
|
||||||
|
* @return 商品总价
|
||||||
|
*/
|
||||||
|
public static Integer calculator(Integer price, Integer count, Integer percent) {
|
||||||
|
price = price * count;
|
||||||
|
if (percent == null) {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
return MoneyUtils.calculateRatePriceFloor(price, (double) (percent / 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算百分比金额
|
||||||
|
*
|
||||||
|
* @param price 金额
|
||||||
|
* @param rate 百分比,例如说 56.77% 则传入 56.77
|
||||||
|
* @param scale 保留小数位数
|
||||||
|
* @param roundingMode 舍入模式
|
||||||
|
*/
|
||||||
|
public static BigDecimal calculateRatePrice(Number price, Number rate, int scale, RoundingMode roundingMode) {
|
||||||
|
return NumberUtil.toBigDecimal(price).multiply(NumberUtil.toBigDecimal(rate)) // 乘以
|
||||||
|
.divide(BigDecimal.valueOf(100), scale, roundingMode); // 除以 100
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分转元
|
||||||
|
*
|
||||||
|
* @param fen 分
|
||||||
|
* @return 元
|
||||||
|
*/
|
||||||
|
public static BigDecimal fenToYuan(int fen) {
|
||||||
|
return new Money(0, fen).getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分转元(字符串)
|
||||||
|
*
|
||||||
|
* 例如说 fen 为 1 时,则结果为 0.01
|
||||||
|
*
|
||||||
|
* @param fen 分
|
||||||
|
* @return 元
|
||||||
|
*/
|
||||||
|
public static String fenToYuanStr(int fen) {
|
||||||
|
return new Money(0, fen).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金额相乘,默认进行四舍五入
|
||||||
|
*
|
||||||
|
* 位数:{@link #PRICE_SCALE}
|
||||||
|
*
|
||||||
|
* @param price 金额
|
||||||
|
* @param count 数量
|
||||||
|
* @return 金额相乘结果
|
||||||
|
*/
|
||||||
|
public static BigDecimal priceMultiply(BigDecimal price, BigDecimal count) {
|
||||||
|
if (price == null || count == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return price.multiply(count).setScale(PRICE_SCALE, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金额相乘(百分比),默认进行四舍五入
|
||||||
|
*
|
||||||
|
* 位数:{@link #PRICE_SCALE}
|
||||||
|
*
|
||||||
|
* @param price 金额
|
||||||
|
* @param percent 百分比
|
||||||
|
* @return 金额相乘结果
|
||||||
|
*/
|
||||||
|
public static BigDecimal priceMultiplyPercent(BigDecimal price, BigDecimal percent) {
|
||||||
|
if (price == null || percent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return price.multiply(percent).divide(PERCENT_100, PRICE_SCALE, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.number;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字的工具类,补全 {@link cn.hutool.core.util.NumberUtil} 的功能
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class NumberUtils {
|
||||||
|
|
||||||
|
public static Long parseLong(String str) {
|
||||||
|
return StrUtil.isNotEmpty(str) ? Long.valueOf(str) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer parseInt(String str) {
|
||||||
|
return StrUtil.isNotEmpty(str) ? Integer.valueOf(str) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过经纬度获取地球上两点之间的距离
|
||||||
|
*
|
||||||
|
* 参考 <<a href="https://gitee.com/dromara/hutool/blob/1caabb586b1f95aec66a21d039c5695df5e0f4c1/hutool-core/src/main/java/cn/hutool/core/util/DistanceUtil.java">DistanceUtil</a>> 实现,目前它已经被 hutool 删除
|
||||||
|
*
|
||||||
|
* @param lat1 经度1
|
||||||
|
* @param lng1 纬度1
|
||||||
|
* @param lat2 经度2
|
||||||
|
* @param lng2 纬度2
|
||||||
|
* @return 距离,单位:千米
|
||||||
|
*/
|
||||||
|
public static double getDistance(double lat1, double lng1, double lat2, double lng2) {
|
||||||
|
double radLat1 = lat1 * Math.PI / 180.0;
|
||||||
|
double radLat2 = lat2 * Math.PI / 180.0;
|
||||||
|
double a = radLat1 - radLat2;
|
||||||
|
double b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
|
||||||
|
double distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
|
||||||
|
+ Math.cos(radLat1) * Math.cos(radLat2)
|
||||||
|
* Math.pow(Math.sin(b / 2), 2)));
|
||||||
|
distance = distance * 6378.137;
|
||||||
|
distance = Math.round(distance * 10000d) / 10000d;
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提供精确的乘法运算
|
||||||
|
*
|
||||||
|
* 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是,如果存在 null,则返回 null
|
||||||
|
*
|
||||||
|
* @param values 多个被乘值
|
||||||
|
* @return 积
|
||||||
|
*/
|
||||||
|
public static BigDecimal mul(BigDecimal... values) {
|
||||||
|
for (BigDecimal value : values) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NumberUtil.mul(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.object;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import co.yixiang.yshop.framework.common.pojo.PageResult;
|
||||||
|
import co.yixiang.yshop.framework.common.util.collection.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bean 工具类
|
||||||
|
*
|
||||||
|
* 1. 默认使用 {@link cn.hutool.core.bean.BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能
|
||||||
|
* 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class BeanUtils {
|
||||||
|
|
||||||
|
public static <T> T toBean(Object source, Class<T> targetClass) {
|
||||||
|
return BeanUtil.toBean(source, targetClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toBean(Object source, Class<T> targetClass, Consumer<T> peek) {
|
||||||
|
T target = toBean(source, targetClass);
|
||||||
|
if (target != null) {
|
||||||
|
peek.accept(target);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <S, T> List<T> toBean(List<S> source, Class<T> targetType) {
|
||||||
|
if (source == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return CollectionUtils.convertList(source, s -> toBean(s, targetType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <S, T> List<T> toBean(List<S> source, Class<T> targetType, Consumer<T> peek) {
|
||||||
|
List<T> list = toBean(source, targetType);
|
||||||
|
if (list != null) {
|
||||||
|
list.forEach(peek);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {
|
||||||
|
return toBean(source, targetType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType, Consumer<T> peek) {
|
||||||
|
if (source == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<T> list = toBean(source.getList(), targetType);
|
||||||
|
if (peek != null) {
|
||||||
|
list.forEach(peek);
|
||||||
|
}
|
||||||
|
return new PageResult<>(list, source.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File renamed without changes.
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.object;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.func.Func1;
|
||||||
|
import cn.hutool.core.lang.func.LambdaUtil;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import co.yixiang.yshop.framework.common.pojo.PageParam;
|
||||||
|
import co.yixiang.yshop.framework.common.pojo.SortablePageParam;
|
||||||
|
import co.yixiang.yshop.framework.common.pojo.SortingField;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link co.yixiang.yshop.framework.common.pojo.PageParam} 工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class PageUtils {
|
||||||
|
|
||||||
|
private static final Object[] ORDER_TYPES = new String[]{SortingField.ORDER_ASC, SortingField.ORDER_DESC};
|
||||||
|
|
||||||
|
public static int getStart(PageParam pageParam) {
|
||||||
|
return (pageParam.getPageNo() - 1) * pageParam.getPageSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建排序字段(默认倒序)
|
||||||
|
*
|
||||||
|
* @param func 排序字段的 Lambda 表达式
|
||||||
|
* @param <T> 排序字段所属的类型
|
||||||
|
* @return 排序字段
|
||||||
|
*/
|
||||||
|
public static <T> SortingField buildSortingField(Func1<T, ?> func) {
|
||||||
|
return buildSortingField(func, SortingField.ORDER_DESC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建排序字段
|
||||||
|
*
|
||||||
|
* @param func 排序字段的 Lambda 表达式
|
||||||
|
* @param order 排序类型 {@link SortingField#ORDER_ASC} {@link SortingField#ORDER_DESC}
|
||||||
|
* @param <T> 排序字段所属的类型
|
||||||
|
* @return 排序字段
|
||||||
|
*/
|
||||||
|
public static <T> SortingField buildSortingField(Func1<T, ?> func, String order) {
|
||||||
|
Assert.isTrue(ArrayUtil.contains(ORDER_TYPES, order), String.format("字段的排序类型只能是 %s/%s", ORDER_TYPES));
|
||||||
|
|
||||||
|
String fieldName = LambdaUtil.getFieldName(func);
|
||||||
|
return new SortingField(fieldName, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建默认的排序字段
|
||||||
|
* 如果排序字段为空,则设置排序字段;否则忽略
|
||||||
|
*
|
||||||
|
* @param sortablePageParam 排序分页查询参数
|
||||||
|
* @param func 排序字段的 Lambda 表达式
|
||||||
|
* @param <T> 排序字段所属的类型
|
||||||
|
*/
|
||||||
|
public static <T> void buildDefaultSortingField(SortablePageParam sortablePageParam, Func1<T, ?> func) {
|
||||||
|
if (sortablePageParam != null && CollUtil.isEmpty(sortablePageParam.getSortingFields())) {
|
||||||
|
sortablePageParam.setSortingFields(singletonList(buildSortingField(func)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.servlet;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||||
|
import co.yixiang.yshop.framework.common.util.json.JsonUtils;
|
||||||
|
import jakarta.servlet.ServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class ServletUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回 JSON 字符串
|
||||||
|
*
|
||||||
|
* @param response 响应
|
||||||
|
* @param object 对象,会序列化成 JSON 字符串
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
|
||||||
|
public static void writeJSON(HttpServletResponse response, Object object) {
|
||||||
|
String content = JsonUtils.toJsonString(object);
|
||||||
|
JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param request 请求
|
||||||
|
* @return ua
|
||||||
|
*/
|
||||||
|
public static String getUserAgent(HttpServletRequest request) {
|
||||||
|
String ua = request.getHeader("User-Agent");
|
||||||
|
return ua != null ? ua : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得请求
|
||||||
|
*
|
||||||
|
* @return HttpServletRequest
|
||||||
|
*/
|
||||||
|
public static HttpServletRequest getRequest() {
|
||||||
|
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||||
|
if (!(requestAttributes instanceof ServletRequestAttributes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUserAgent() {
|
||||||
|
HttpServletRequest request = getRequest();
|
||||||
|
if (request == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getUserAgent(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientIP() {
|
||||||
|
HttpServletRequest request = getRequest();
|
||||||
|
if (request == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JakartaServletUtil.getClientIP(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isJsonRequest(ServletRequest request) {
|
||||||
|
return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getBody(HttpServletRequest request) {
|
||||||
|
// 只有在 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取
|
||||||
|
if (isJsonRequest(request)) {
|
||||||
|
return JakartaServletUtil.getBody(request);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getBodyBytes(HttpServletRequest request) {
|
||||||
|
// 只有在 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取
|
||||||
|
if (isJsonRequest(request)) {
|
||||||
|
return JakartaServletUtil.getBodyBytes(request);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientIP(HttpServletRequest request) {
|
||||||
|
return JakartaServletUtil.getClientIP(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> getParamMap(HttpServletRequest request) {
|
||||||
|
return JakartaServletUtil.getParamMap(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRequstHost(HttpServletRequest request){
|
||||||
|
String port = ":" + request.getServerPort();
|
||||||
|
if(request.getServerPort() == 80 || request.getServerPort() == 443){
|
||||||
|
port = "";
|
||||||
|
}
|
||||||
|
return request.getScheme()+"://" + request.getServerName() + port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回附件
|
||||||
|
*
|
||||||
|
* @param response 响应
|
||||||
|
* @param filename 文件名
|
||||||
|
* @param content 附件内容
|
||||||
|
*/
|
||||||
|
public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
|
||||||
|
// 设置 header 和 contentType
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
|
||||||
|
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||||
|
// 输出附件
|
||||||
|
IoUtil.write(response.getOutputStream(), false, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+10
-3
@@ -3,7 +3,7 @@ package co.yixiang.yshop.framework.common.util.spring;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||||
import org.springframework.core.ParameterNameDiscoverer;
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
@@ -24,7 +24,13 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class SpringExpressionUtils {
|
public class SpringExpressionUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring EL 表达式解析器
|
||||||
|
*/
|
||||||
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||||
|
/**
|
||||||
|
* 参数名发现器
|
||||||
|
*/
|
||||||
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
||||||
|
|
||||||
private SpringExpressionUtils() {
|
private SpringExpressionUtils() {
|
||||||
@@ -37,7 +43,7 @@ public class SpringExpressionUtils {
|
|||||||
* @param expressionString EL 表达式数组
|
* @param expressionString EL 表达式数组
|
||||||
* @return 执行界面
|
* @return 执行界面
|
||||||
*/
|
*/
|
||||||
public static Object parseExpression(ProceedingJoinPoint joinPoint, String expressionString) {
|
public static Object parseExpression(JoinPoint joinPoint, String expressionString) {
|
||||||
Map<String, Object> result = parseExpressions(joinPoint, Collections.singletonList(expressionString));
|
Map<String, Object> result = parseExpressions(joinPoint, Collections.singletonList(expressionString));
|
||||||
return result.get(expressionString);
|
return result.get(expressionString);
|
||||||
}
|
}
|
||||||
@@ -49,7 +55,7 @@ public class SpringExpressionUtils {
|
|||||||
* @param expressionStrings EL 表达式数组
|
* @param expressionStrings EL 表达式数组
|
||||||
* @return 结果,key 为表达式,value 为对应值
|
* @return 结果,key 为表达式,value 为对应值
|
||||||
*/
|
*/
|
||||||
public static Map<String, Object> parseExpressions(ProceedingJoinPoint joinPoint, List<String> expressionStrings) {
|
public static Map<String, Object> parseExpressions(JoinPoint joinPoint, List<String> expressionStrings) {
|
||||||
// 如果为空,则不进行解析
|
// 如果为空,则不进行解析
|
||||||
if (CollUtil.isEmpty(expressionStrings)) {
|
if (CollUtil.isEmpty(expressionStrings)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
@@ -79,4 +85,5 @@ public class SpringExpressionUtils {
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.spring;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring 工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class SpringUtils extends SpringUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为生产环境
|
||||||
|
*
|
||||||
|
* @return 是否生产环境
|
||||||
|
*/
|
||||||
|
public static boolean isProd() {
|
||||||
|
String activeProfile = getActiveProfile();
|
||||||
|
return Objects.equals("prod", activeProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.string;
|
||||||
|
|
||||||
|
import cn.hutool.core.text.StrPool;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class StrUtils {
|
||||||
|
|
||||||
|
public static String maxLength(CharSequence str, int maxLength) {
|
||||||
|
return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给定字符串是否以任何一个字符串开始
|
||||||
|
* 给定字符串和数组为空都返回 false
|
||||||
|
*
|
||||||
|
* @param str 给定字符串
|
||||||
|
* @param prefixes 需要检测的开始字符串
|
||||||
|
* @since 3.0.6
|
||||||
|
*/
|
||||||
|
public static boolean startWithAny(String str, Collection<String> prefixes) {
|
||||||
|
if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CharSequence suffix : prefixes) {
|
||||||
|
if (StrUtil.startWith(str, suffix, false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Long> splitToLong(String value, CharSequence separator) {
|
||||||
|
long[] longs = StrUtil.splitToLong(value, separator);
|
||||||
|
return Arrays.stream(longs).boxed().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Long> splitToLongSet(String value) {
|
||||||
|
return splitToLongSet(value, StrPool.COMMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Long> splitToLongSet(String value, CharSequence separator) {
|
||||||
|
long[] longs = StrUtil.splitToLong(value, separator);
|
||||||
|
return Arrays.stream(longs).boxed().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Integer> splitToInteger(String value, CharSequence separator) {
|
||||||
|
int[] integers = StrUtil.splitToInt(value, separator);
|
||||||
|
return Arrays.stream(integers).boxed().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除字符串中,包含指定字符串的行
|
||||||
|
*
|
||||||
|
* @param content 字符串
|
||||||
|
* @param sequence 包含的字符串
|
||||||
|
* @return 移除后的字符串
|
||||||
|
*/
|
||||||
|
public static String removeLineContains(String content, String sequence) {
|
||||||
|
if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
return Arrays.stream(content.split("\n"))
|
||||||
|
.filter(line -> !line.contains(sequence))
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+11
-3
@@ -1,11 +1,13 @@
|
|||||||
package co.yixiang.yshop.framework.common.util.validation;
|
package co.yixiang.yshop.framework.common.util.validation;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import javax.validation.ConstraintViolation;
|
import jakarta.validation.ConstraintViolation;
|
||||||
import javax.validation.ConstraintViolationException;
|
import jakarta.validation.ConstraintViolationException;
|
||||||
import javax.validation.Validator;
|
import jakarta.validation.Validation;
|
||||||
|
import jakarta.validation.Validator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -37,6 +39,12 @@ public class ValidationUtils {
|
|||||||
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validate(Object object, Class<?>... groups) {
|
||||||
|
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||||
|
Assert.notNull(validator);
|
||||||
|
validate(validator, object, groups);
|
||||||
|
}
|
||||||
|
|
||||||
public static void validate(Validator validator, Object object, Class<?>... groups) {
|
public static void validate(Validator validator, Object object, Class<?>... groups) {
|
||||||
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
||||||
if (CollUtil.isNotEmpty(constraintViolations)) {
|
if (CollUtil.isNotEmpty(constraintViolations)) {
|
||||||
-1
@@ -3,7 +3,6 @@ package co.yixiang.yshop.framework.common.util.yshop;
|
|||||||
import cn.hutool.core.util.NumberUtil;
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定位工具
|
* 定位工具
|
||||||
* @author hupeng
|
* @author hupeng
|
||||||
+3
-3
@@ -2,8 +2,8 @@ package co.yixiang.yshop.framework.common.validation;
|
|||||||
|
|
||||||
import co.yixiang.yshop.framework.common.core.IntArrayValuable;
|
import co.yixiang.yshop.framework.common.core.IntArrayValuable;
|
||||||
|
|
||||||
import javax.validation.Constraint;
|
import jakarta.validation.Constraint;
|
||||||
import javax.validation.Payload;
|
import jakarta.validation.Payload;
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
@Target({
|
@Target({
|
||||||
@@ -17,7 +17,7 @@ import java.lang.annotation.*;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
@Constraint(
|
@Constraint(
|
||||||
validatedBy = InEnumValidator.class
|
validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}
|
||||||
)
|
)
|
||||||
public @interface InEnum {
|
public @interface InEnum {
|
||||||
|
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.validation;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import co.yixiang.yshop.framework.common.core.IntArrayValuable;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class InEnumCollectionValidator implements ConstraintValidator<InEnum, Collection<Integer>> {
|
||||||
|
|
||||||
|
private List<Integer> values;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(InEnum annotation) {
|
||||||
|
IntArrayValuable[] values = annotation.value().getEnumConstants();
|
||||||
|
if (values.length == 0) {
|
||||||
|
this.values = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Collection<Integer> list, ConstraintValidatorContext context) {
|
||||||
|
// 校验通过
|
||||||
|
if (CollUtil.containsAll(values, list)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
|
||||||
|
context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
|
||||||
|
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
|
||||||
|
.replaceAll("\\{value}", CollUtil.join(list, ","))).addConstraintViolation(); // 重新添加错误提示语句
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
+2
-2
@@ -2,8 +2,8 @@ package co.yixiang.yshop.framework.common.validation;
|
|||||||
|
|
||||||
import co.yixiang.yshop.framework.common.core.IntArrayValuable;
|
import co.yixiang.yshop.framework.common.core.IntArrayValuable;
|
||||||
|
|
||||||
import javax.validation.ConstraintValidator;
|
import jakarta.validation.ConstraintValidator;
|
||||||
import javax.validation.ConstraintValidatorContext;
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
package co.yixiang.yshop.framework.common.validation;
|
package co.yixiang.yshop.framework.common.validation;
|
||||||
|
|
||||||
import javax.validation.Constraint;
|
import jakarta.validation.Constraint;
|
||||||
import javax.validation.Payload;
|
import jakarta.validation.Payload;
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
@Target({
|
@Target({
|
||||||
+2
-2
@@ -3,8 +3,8 @@ package co.yixiang.yshop.framework.common.validation;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import co.yixiang.yshop.framework.common.util.validation.ValidationUtils;
|
import co.yixiang.yshop.framework.common.util.validation.ValidationUtils;
|
||||||
|
|
||||||
import javax.validation.ConstraintValidator;
|
import jakarta.validation.ConstraintValidator;
|
||||||
import javax.validation.ConstraintValidatorContext;
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
|
||||||
public class MobileValidator implements ConstraintValidator<Mobile, String> {
|
public class MobileValidator implements ConstraintValidator<Mobile, String> {
|
||||||
|
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.validation;
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint;
|
||||||
|
import jakarta.validation.Payload;
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Target({
|
||||||
|
ElementType.METHOD,
|
||||||
|
ElementType.FIELD,
|
||||||
|
ElementType.ANNOTATION_TYPE,
|
||||||
|
ElementType.CONSTRUCTOR,
|
||||||
|
ElementType.PARAMETER,
|
||||||
|
ElementType.TYPE_USE
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Constraint(
|
||||||
|
validatedBy = TelephoneValidator.class
|
||||||
|
)
|
||||||
|
public @interface Telephone {
|
||||||
|
|
||||||
|
String message() default "电话格式不正确";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.validation;
|
||||||
|
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.util.PhoneUtil;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
|
||||||
|
public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(Telephone annotation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||||
|
// 如果手机号为空,默认不校验,即校验通过
|
||||||
|
if (CharSequenceUtil.isEmpty(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 校验手机
|
||||||
|
return PhoneUtil.isTel(value) || PhoneUtil.isPhone(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File renamed without changes.
+64
@@ -0,0 +1,64 @@
|
|||||||
|
package co.yixiang.yshop.framework.common.util.collection;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link CollectionUtils} 的单元测试
|
||||||
|
*/
|
||||||
|
public class CollectionUtilsTest {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
private static class Dog {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDiffList() {
|
||||||
|
// 准备参数
|
||||||
|
Collection<Dog> oldList = Arrays.asList(
|
||||||
|
new Dog(1, "花花", "hh"),
|
||||||
|
new Dog(2, "旺财", "wc")
|
||||||
|
);
|
||||||
|
Collection<Dog> newList = Arrays.asList(
|
||||||
|
new Dog(null, "花花2", "hh"),
|
||||||
|
new Dog(null, "小白", "xb")
|
||||||
|
);
|
||||||
|
BiFunction<Dog, Dog, Boolean> sameFunc = (oldObj, newObj) -> {
|
||||||
|
boolean same = oldObj.getCode().equals(newObj.getCode());
|
||||||
|
// 如果相等的情况下,需要设置下 id,后续好更新
|
||||||
|
if (same) {
|
||||||
|
newObj.setId(oldObj.getId());
|
||||||
|
}
|
||||||
|
return same;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
List<List<Dog>> result = CollectionUtils.diffList(oldList, newList, sameFunc);
|
||||||
|
// 断言
|
||||||
|
assertEquals(result.size(), 3);
|
||||||
|
// 断言 create
|
||||||
|
assertEquals(result.get(0).size(), 1);
|
||||||
|
assertEquals(result.get(0).get(0), new Dog(null, "小白", "xb"));
|
||||||
|
// 断言 update
|
||||||
|
assertEquals(result.get(1).size(), 1);
|
||||||
|
assertEquals(result.get(1).get(0), new Dog(1, "花花2", "hh"));
|
||||||
|
// 断言 delete
|
||||||
|
assertEquals(result.get(2).size(), 1);
|
||||||
|
assertEquals(result.get(2).get(0), new Dog(2, "旺财", "wc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>yshop-framework</artifactId>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>yshop-spring-boot-starter-biz-data-permission</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>数据权限</description>
|
||||||
|
<url>https://gitee.com/guchengwuyue/yshop-drink</url>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Web 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-security</artifactId>
|
||||||
|
<optional>true</optional> <!-- 可选,如果使用 DeptDataPermissionRule 必须提供 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- DB 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-mybatis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 业务组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-module-system-api</artifactId> <!-- 需要使用它,进行数据权限的获取 -->
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+5
-3
@@ -5,7 +5,6 @@ import co.yixiang.yshop.framework.common.util.collection.SetUtils;
|
|||||||
import co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;
|
import co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;
|
||||||
import co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
import co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
||||||
import co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;
|
import co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;
|
||||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||||
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
|
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
|
||||||
@@ -497,7 +496,8 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
|
|||||||
Expression allExpression = null;
|
Expression allExpression = null;
|
||||||
for (DataPermissionRule rule : ContextHolder.getRules()) {
|
for (DataPermissionRule rule : ContextHolder.getRules()) {
|
||||||
// 判断表名是否匹配
|
// 判断表名是否匹配
|
||||||
if (!rule.getTableNames().contains(table.getName())) {
|
String tableName = MyBatisUtils.getTableName(table);
|
||||||
|
if (!rule.getTableNames().contains(tableName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 如果有匹配的规则,说明可重写。
|
// 如果有匹配的规则,说明可重写。
|
||||||
@@ -506,8 +506,10 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
|
|||||||
ContextHolder.setRewrite(true);
|
ContextHolder.setRewrite(true);
|
||||||
|
|
||||||
// 单条规则的条件
|
// 单条规则的条件
|
||||||
String tableName = MyBatisUtils.getTableName(table);
|
|
||||||
Expression oneExpress = rule.getExpression(tableName, table.getAlias());
|
Expression oneExpress = rule.getExpression(tableName, table.getAlias());
|
||||||
|
if (oneExpress == null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// 拼接到 allExpression 中
|
// 拼接到 allExpression 中
|
||||||
allExpression = allExpression == null ? oneExpress
|
allExpression = allExpression == null ? oneExpress
|
||||||
: new AndExpression(allExpression, oneExpress);
|
: new AndExpression(allExpression, oneExpress);
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4
-7
@@ -7,7 +7,6 @@ import co.yixiang.yshop.framework.common.enums.UserTypeEnum;
|
|||||||
import co.yixiang.yshop.framework.common.util.collection.CollectionUtils;
|
import co.yixiang.yshop.framework.common.util.collection.CollectionUtils;
|
||||||
import co.yixiang.yshop.framework.common.util.json.JsonUtils;
|
import co.yixiang.yshop.framework.common.util.json.JsonUtils;
|
||||||
import co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;
|
import co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;
|
||||||
import co.yixiang.yshop.framework.expression.OrExpressionX;
|
|
||||||
import co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;
|
import co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;
|
import co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;
|
||||||
import co.yixiang.yshop.framework.security.core.LoginUser;
|
import co.yixiang.yshop.framework.security.core.LoginUser;
|
||||||
@@ -17,10 +16,8 @@ import co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespD
|
|||||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.sf.jsqlparser.expression.Alias;
|
import net.sf.jsqlparser.expression.*;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||||
import net.sf.jsqlparser.expression.LongValue;
|
|
||||||
import net.sf.jsqlparser.expression.NullValue;
|
|
||||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||||
@@ -36,7 +33,7 @@ import java.util.Set;
|
|||||||
* 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
|
* 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
|
||||||
*
|
*
|
||||||
* 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
|
* 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
|
||||||
* 1. 一般情况下,dept_id 不进行修改,则会导致用户看到之前的数据。【yshop-server 采用该方案】
|
* 1. 一般情况下,dept_id 不进行修改,则会导致用户看不到之前的数据。【yshop-server 采用该方案】
|
||||||
* 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
|
* 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
|
||||||
* 1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
|
* 1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
|
||||||
* 最终过滤条件是 WHERE dept_id = ?
|
* 最终过滤条件是 WHERE dept_id = ?
|
||||||
@@ -144,7 +141,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
|||||||
return deptExpression;
|
return deptExpression;
|
||||||
}
|
}
|
||||||
// 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
|
// 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
|
||||||
return new OrExpressionX(deptExpression, userExpression);
|
return new Parenthesis(new OrExpression(deptExpression, userExpression));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
|
private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
|
||||||
File renamed without changes.
File renamed without changes.
+63
@@ -0,0 +1,63 @@
|
|||||||
|
package co.yixiang.yshop.framework.datapermission.core.util;
|
||||||
|
|
||||||
|
import co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;
|
||||||
|
import co.yixiang.yshop.framework.datapermission.core.aop.DataPermissionContextHolder;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限 Util
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
public class DataPermissionUtils {
|
||||||
|
|
||||||
|
private static DataPermission DATA_PERMISSION_DISABLE;
|
||||||
|
|
||||||
|
@DataPermission(enable = false)
|
||||||
|
@SneakyThrows
|
||||||
|
private static DataPermission getDisableDataPermissionDisable() {
|
||||||
|
if (DATA_PERMISSION_DISABLE == null) {
|
||||||
|
DATA_PERMISSION_DISABLE = DataPermissionUtils.class
|
||||||
|
.getDeclaredMethod("getDisableDataPermissionDisable")
|
||||||
|
.getAnnotation(DataPermission.class);
|
||||||
|
}
|
||||||
|
return DATA_PERMISSION_DISABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略数据权限,执行对应的逻辑
|
||||||
|
*
|
||||||
|
* @param runnable 逻辑
|
||||||
|
*/
|
||||||
|
public static void executeIgnore(Runnable runnable) {
|
||||||
|
DataPermission dataPermission = getDisableDataPermissionDisable();
|
||||||
|
DataPermissionContextHolder.add(dataPermission);
|
||||||
|
try {
|
||||||
|
// 执行 runnable
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
DataPermissionContextHolder.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略数据权限,执行对应的逻辑
|
||||||
|
*
|
||||||
|
* @param callable 逻辑
|
||||||
|
* @return 执行结果
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
public static <T> T executeIgnore(Callable<T> callable) {
|
||||||
|
DataPermission dataPermission = getDisableDataPermissionDisable();
|
||||||
|
DataPermissionContextHolder.add(dataPermission);
|
||||||
|
try {
|
||||||
|
// 执行 callable
|
||||||
|
return callable.call();
|
||||||
|
} finally {
|
||||||
|
DataPermissionContextHolder.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+5
-2
@@ -4,11 +4,11 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import co.yixiang.yshop.framework.common.enums.UserTypeEnum;
|
import co.yixiang.yshop.framework.common.enums.UserTypeEnum;
|
||||||
import co.yixiang.yshop.framework.common.util.collection.SetUtils;
|
import co.yixiang.yshop.framework.common.util.collection.SetUtils;
|
||||||
import co.yixiang.yshop.module.system.api.permission.PermissionApi;
|
|
||||||
import co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
|
||||||
import co.yixiang.yshop.framework.security.core.LoginUser;
|
import co.yixiang.yshop.framework.security.core.LoginUser;
|
||||||
import co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;
|
import co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
import co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;
|
import co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import co.yixiang.yshop.module.system.api.permission.PermissionApi;
|
||||||
|
import co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
||||||
import net.sf.jsqlparser.expression.Alias;
|
import net.sf.jsqlparser.expression.Alias;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -23,6 +23,7 @@ import static co.yixiang.yshop.framework.datapermission.core.rule.dept.DeptDataP
|
|||||||
import static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;
|
import static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;
|
||||||
import static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;
|
import static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
import static org.mockito.Mockito.mockStatic;
|
import static org.mockito.Mockito.mockStatic;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -73,6 +74,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
|
|||||||
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
|
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
|
||||||
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||||
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
|
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
|
||||||
|
// mock 方法(permissionApi 返回 null)
|
||||||
|
when(permissionApi.getDeptDataPermission(eq(loginUser.getId()))).thenReturn(null);
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
NullPointerException exception = assertThrows(NullPointerException.class,
|
NullPointerException exception = assertThrows(NullPointerException.class,
|
||||||
File renamed without changes.
@@ -0,0 +1,54 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>yshop-framework</artifactId>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>yshop-spring-boot-starter-biz-ip</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>IP 拓展,支持如下功能:
|
||||||
|
1. IP 功能:查询 IP 对应的城市信息
|
||||||
|
基于 https://gitee.com/lionsoul/ip2region 实现
|
||||||
|
2. 城市功能:查询城市编码对应的城市信息
|
||||||
|
基于 https://github.com/modood/Administrative-divisions-of-China 实现
|
||||||
|
</description>
|
||||||
|
<url>https://gitee.com/guchengwuyue/yshop-drink</url>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- IP地址检索 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.lionsoul</groupId>
|
||||||
|
<artifactId>ip2region</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
<artifactId>yshop-spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
File renamed without changes.
File renamed without changes.
+214
@@ -0,0 +1,214 @@
|
|||||||
|
package co.yixiang.yshop.framework.ip.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.text.csv.CsvRow;
|
||||||
|
import cn.hutool.core.text.csv.CsvUtil;
|
||||||
|
import co.yixiang.yshop.framework.common.util.object.ObjectUtils;
|
||||||
|
import co.yixiang.yshop.framework.ip.core.Area;
|
||||||
|
import co.yixiang.yshop.framework.ip.core.enums.AreaTypeEnum;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;
|
||||||
|
import static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.findFirst;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域工具类
|
||||||
|
*
|
||||||
|
* @author yshop
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class AreaUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 SEARCHER
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||||
|
private final static AreaUtils INSTANCE = new AreaUtils();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Area 内存缓存,提升访问速度
|
||||||
|
*/
|
||||||
|
private static Map<Integer, Area> areas;
|
||||||
|
|
||||||
|
private AreaUtils() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
areas = new HashMap<>();
|
||||||
|
areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0,
|
||||||
|
null, new ArrayList<>()));
|
||||||
|
// 从 csv 中加载数据
|
||||||
|
List<CsvRow> rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows();
|
||||||
|
rows.remove(0); // 删除 header
|
||||||
|
for (CsvRow row : rows) {
|
||||||
|
// 创建 Area 对象
|
||||||
|
Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)),
|
||||||
|
null, new ArrayList<>());
|
||||||
|
// 添加到 areas 中
|
||||||
|
areas.put(area.getId(), area);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取
|
||||||
|
for (CsvRow row : rows) {
|
||||||
|
Area area = areas.get(Integer.valueOf(row.get(0))); // 自己
|
||||||
|
Area parent = areas.get(Integer.valueOf(row.get(3))); // 父
|
||||||
|
Assert.isTrue(area != parent, "{}:父子节点相同", area.getName());
|
||||||
|
area.setParent(parent);
|
||||||
|
parent.getChildren().add(area);
|
||||||
|
}
|
||||||
|
log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得指定编号对应的区域
|
||||||
|
*
|
||||||
|
* @param id 区域编号
|
||||||
|
* @return 区域
|
||||||
|
*/
|
||||||
|
public static Area getArea(Integer id) {
|
||||||
|
return areas.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得指定区域对应的编号
|
||||||
|
*
|
||||||
|
* @param pathStr 区域路径,例如说:河南省/石家庄市/新华区
|
||||||
|
* @return 区域
|
||||||
|
*/
|
||||||
|
public static Area parseArea(String pathStr) {
|
||||||
|
String[] paths = pathStr.split("/");
|
||||||
|
Area area = null;
|
||||||
|
for (String path : paths) {
|
||||||
|
if (area == null) {
|
||||||
|
area = findFirst(areas.values(), item -> item.getName().equals(path));
|
||||||
|
} else {
|
||||||
|
area = findFirst(area.getChildren(), item -> item.getName().equals(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有节点的全路径名称如:河南省/石家庄市/新华区
|
||||||
|
*
|
||||||
|
* @param areas 地区树
|
||||||
|
* @return 所有节点的全路径名称
|
||||||
|
*/
|
||||||
|
public static List<String> getAreaNodePathList(List<Area> areas) {
|
||||||
|
List<String> paths = new ArrayList<>();
|
||||||
|
areas.forEach(area -> getAreaNodePathList(area, "", paths));
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建一棵树的所有节点的全路径名称,并将其存储为 "祖先/父级/子级" 的形式
|
||||||
|
*
|
||||||
|
* @param node 父节点
|
||||||
|
* @param path 全路径名称
|
||||||
|
* @param paths 全路径名称列表,省份/城市/地区
|
||||||
|
*/
|
||||||
|
private static void getAreaNodePathList(Area node, String path, List<String> paths) {
|
||||||
|
if (node == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 构建当前节点的路径
|
||||||
|
String currentPath = path.isEmpty() ? node.getName() : path + "/" + node.getName();
|
||||||
|
paths.add(currentPath);
|
||||||
|
// 递归遍历子节点
|
||||||
|
for (Area child : node.getChildren()) {
|
||||||
|
getAreaNodePathList(child, currentPath, paths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化区域
|
||||||
|
*
|
||||||
|
* @param id 区域编号
|
||||||
|
* @return 格式化后的区域
|
||||||
|
*/
|
||||||
|
public static String format(Integer id) {
|
||||||
|
return format(id, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化区域
|
||||||
|
*
|
||||||
|
* 例如说:
|
||||||
|
* 1. id = “静安区”时:上海 上海市 静安区
|
||||||
|
* 2. id = “上海市”时:上海 上海市
|
||||||
|
* 3. id = “上海”时:上海
|
||||||
|
* 4. id = “美国”时:美国
|
||||||
|
* 当区域在中国时,默认不显示中国
|
||||||
|
*
|
||||||
|
* @param id 区域编号
|
||||||
|
* @param separator 分隔符
|
||||||
|
* @return 格式化后的区域
|
||||||
|
*/
|
||||||
|
public static String format(Integer id, String separator) {
|
||||||
|
// 获得区域
|
||||||
|
Area area = areas.get(id);
|
||||||
|
if (area == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < AreaTypeEnum.values().length; i++) { // 避免死循环
|
||||||
|
sb.insert(0, area.getName());
|
||||||
|
// “递归”父节点
|
||||||
|
area = area.getParent();
|
||||||
|
if (area == null
|
||||||
|
|| ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sb.insert(0, separator);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定类型的区域列表
|
||||||
|
*
|
||||||
|
* @param type 区域类型
|
||||||
|
* @param func 转换函数
|
||||||
|
* @param <T> 结果类型
|
||||||
|
* @return 区域列表
|
||||||
|
*/
|
||||||
|
public static <T> List<T> getByType(AreaTypeEnum type, Function<Area, T> func) {
|
||||||
|
return convertList(areas.values(), func, area -> type.getType().equals(area.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据区域编号、上级区域类型,获取上级区域编号
|
||||||
|
*
|
||||||
|
* @param id 区域编号
|
||||||
|
* @param type 区域类型
|
||||||
|
* @return 上级区域编号
|
||||||
|
*/
|
||||||
|
public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) {
|
||||||
|
for (int i = 0; i < Byte.MAX_VALUE; i++) {
|
||||||
|
Area area = AreaUtils.getArea(id);
|
||||||
|
if (area == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 情况一:匹配到,返回它
|
||||||
|
if (type.getType().equals(area.getType())) {
|
||||||
|
return area.getId();
|
||||||
|
}
|
||||||
|
// 情况二:找到根节点,返回空
|
||||||
|
if (area.getParent() == null || area.getParent().getId() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 其它:继续向上查找
|
||||||
|
id = area.getParent().getId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loaded 100 of 3676 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user