Compare commits

..

26 Commits

Author SHA1 Message Date
a4d1731862 release: v2.11.0 2025-04-13 17:17:59 +08:00
ce38377a98 docs: 更新官网相关链接 2025-04-12 19:34:59 +08:00
0463c74aa4 refactor(log): 优化日志拦截器配置 2025-04-09 21:38:20 +08:00
8766f11eb2 refactor(extension/crud): 将 @DictField 注解重命名为 @DictModel,用于更清晰地表示字典结构映射 2025-04-09 20:17:39 +08:00
65d3cef093 chore: 优化 Issue 模板 2025-04-07 21:04:22 +08:00
615dfdd03f feat(cache/redisson): 添加缓存键前缀支持 2025-04-04 18:52:00 +08:00
efaef9d7e6 refactor: 统一配置启用属性描述 2025-04-04 17:34:18 +08:00
45da758dee refactor(extension/crud): 重构删除接口,以解决批量删除时 URL 中 ID 列表过长问题 2025-04-04 17:29:00 +08:00
a87104830f build: sa-token 1.40.0 => 1.41.0 2025-04-04 17:19:52 +08:00
1c65191b8a build: 解决部分传递依赖漏洞问题 2025-04-02 20:35:58 +08:00
08068cb9f7 refactor(web): 优化部分代码 2025-04-02 20:11:34 +08:00
jasmine
49b1b6a690 feat(web): 添加 Undertow 自定义配置和默认配置,默认禁止三个不安全的 HTTP 方法(如 CONNECT、TRACE、TRACK) 2025-04-02 08:39:30 +00:00
jiang4yu
1d4f3a33b9 build: 调整部分bom 2025-04-02 07:43:17 +00:00
jasmine
5a2621a030 refactor: 修改构建本部门及以下数据权限表达式 以支持PostgreSQL 2025-04-02 03:59:41 +00:00
f2ba10beae refactor: 优化部分代码及方法命名,移除 SpringWebUtils 部分重复方法 2025-04-01 22:08:54 +08:00
吴泽威
7e8a15ae8a perf: 删除多余依赖,格式化代码
删除幂等模块的 springboot-aop 依赖
删除 web 模块的 springboot-Validation 依赖
2025-04-01 16:48:16 +08:00
吴泽威
199a83fbea fix(log/core): 修复访问日志json数组打印
对于请求参数的 json 数组打印和处理的问题修复
适配 json 数组打印处理,json 模块增加 JSONUtil 和
JsonBuilder
2025-04-01 16:44:50 +08:00
吴泽威
ca2c88651f refactor(log/core): 重构请求和响应信息获取
web 模块 ServletUtils整理
log/core 模块 删除 RecordableHttpRequest和
RecordableHttpResponse
2025-04-01 15:58:02 +08:00
吴泽威
a6a44cd461 perf(log): 访问日志过滤资源路径 2025-04-01 15:34:54 +08:00
5822d073fb build: 新增 continew-starter-bom 模块,用于集中管理所有子模块版本 2025-03-31 21:29:14 +08:00
e24256818d build: 调整 sa-token 版本锁定为 bom 方式(PR by iang4yu) 2025-03-31 21:11:04 +08:00
3e0dd83e26 revert: 还原 JetCache bom 2025-03-31 21:10:01 +08:00
acfd1daf4b chore(extension/datapermission): 修改配置属性描述 2025-03-30 12:03:56 +08:00
ae2b898e57 build(core): 替换 aspectjweaver 依赖为 Spring Boot Starter AOP 2025-03-30 12:03:27 +08:00
f662b74061 build(cache/jetcache): 排除 redisson 依赖以避免冲突 2025-03-30 11:56:24 +08:00
jiang4yu
e2d8f45206 build: 采取 bom 方式来管理 JetCache 依赖 2025-03-30 03:51:52 +00:00
63 changed files with 1867 additions and 1079 deletions

View File

@@ -1,5 +1,5 @@
name: "\U0001F41E Bug 报告"
description: Create a report to help us improve
description: 在使用 xxx 功能时出现异常
labels: ['bug: pending triage']
body:
- type: markdown
@@ -10,20 +10,22 @@ body:
id: checkboxes
attributes:
label: 请您确认
description: 在提交 Issue 之前,请确保执行过以下操作。
description: 在提交 Bug 之前,请确保执行过以下操作。
options:
- label: 尝试[最新版本](https://central.sonatype.com/artifact/top.continew/continew-starter/versions),仍有相同问题
- label: 重启项目和 IDE 后,仍然能够复现此问题
required: true
- label: 阅读[使用指南](https://continew.top/starter/intro/what-is.html)
- label: 查阅过 [使用指南](https://continew.top/starter/guide/introduction.html) 和 [常见问题](https://continew.top/starter/faq.html) ,仍无解决方法
required: true
- label: 查找[常见问题](https://continew.top/faq.html)
- label: 根据报错信息(自行翻译英文)百度或 Google 后,仍无法解决
required: true
- label: 根据报错信息(自行翻译英文)百度或 Google 一下
- label: 尝试 [最新版本](https://central.sonatype.com/artifact/top.continew/continew-starter/versions)(还可以尝试本地编译安装 dev 分支的最新快照版本),仍有相同问题
required: true
- label: 阅读源码并在 IDE 中进行断点调试
- label: 搜索了项目 Issues没有其他人提交过类似的 Bug如果对应 Bug 尚未解决,您可以先订阅关注该 Issue为了方便后来者查找问题解决方法请避免创建重复的 Issue
required: true
- label: 阅读了源码并在 IDE 中进行断点调试
required: false
- label: 是否愿意为您提出的 Bug 提交 PR
required: false
- label: 搜索是否有其他人提交过类似的 Issue如果对应 Issue 尚未解决,您可以先订阅关注该 Issue为了方便后来者查找问题解决方法请尽量避免创建重复的 Issue
required: true
validations:
required: true
- type: textarea

View File

@@ -1,5 +1,5 @@
name: "\U0001F680 新 Feature 建议"
description: Suggest an idea for this project
description: 我希望增加 xxx 功能;现有的 xxx 功能不好用...
body:
- type: markdown
attributes:
@@ -9,17 +9,17 @@ body:
id: checkboxes
attributes:
label: 请您确认
description: 在提交 Feature 之前,请确执行过以下操作。
description: 在提交 Feature 之前,请确执行过以下操作。
options:
- label: 阅读[使用指南](https://continew.top/starter/intro/what-is.html)
- label: 尝试 [最新版本](https://central.sonatype.com/artifact/top.continew/continew-starter/versions)(还可以尝试本地编译安装 dev 分支的最新快照版本),仍没有该功能
required: true
- label: [常见问题](https://continew.top/faq.html)
- label: 阅过 [使用指南](https://continew.top/starter/guide/introduction.html) 和 [常见问题](https://continew.top/starter/faq.html) ,仍然认为很有必要
required: true
- label: [需求墙](https://continew.top/require.html)计划
- label: 阅过 [需求墙](https://continew.top/starter/other/feature.html),仍没有该功能计划
required: true
- label: 搜索是否有其他人提交过类似的 Feature如果对应 Feature 尚未完成,您可以先订阅关注该 Feature为了方便后来者查找问题解决方法,请尽量避免创建重复的 Feature
- label: 搜索了项目 Issues有其他人提交过类似的 Feature如果对应 Feature 尚未实现,您可以先订阅关注该 Issue为了方便后来者查找问题解决方法,请避免创建重复的 Issue
required: true
- label: 是否愿意为您提出的 Feature 提交 PR
- label: 是否愿意为您提出的 Feature 提交 PR
required: false
validations:
required: true

View File

@@ -1,3 +1,38 @@
## [v2.11.0](https://github.com/continew-org/continew-starter/compare/v2.10.0...v2.11.0) (2025-04-13)
### ✨ 新特性
- 【web】添加 Undertow 自定义配置和默认配置,默认禁止三个不安全的 HTTP 方法(如 CONNECT、TRACE、TRACK (Gitee#50@httpsjt) ([49b1b6a](https://github.com/continew-org/continew-starter/commit/49b1b6a69073095859c7e692f4e5908144eb4ae6)) ([08068cb](https://github.com/continew-org/continew-starter/commit/08068cb9f7ad7e56ec4df662e2c1e8bf04cb45a1))
- 新增 continew-starter-bom 模块,用于集中管理所有子模块版本 ([5822d07](https://github.com/continew-org/continew-starter/commit/5822d073fb28c7178409010e6e4f9e78c1ae2d5a))
- 【cache/redisson】添加缓存键前缀支持 ([615dfdd](https://github.com/continew-org/continew-starter/commit/615dfdd03fb0bc8989b8a788c9babd30709ac643))
### 💎 功能优化
- 【dependencies】采取 bom 方式来管理 JetCache 依赖 (Gitee#44@jiang4yu) ([e2d8f45](https://github.com/continew-org/continew-starter/commit/e2d8f45206a55e333c26a48c501efbb82c89beea)) ([f662b74](https://github.com/continew-org/continew-starter/commit/f662b740610da3e1ff4c0fadf2e5b2a188b06d73)) ([3e0dd83](https://github.com/continew-org/continew-starter/commit/3e0dd83e2664e57d61c37e4ea7afa618c322b984))
- 替换 aspectjweaver 依赖为 Spring Boot Starter AOP ([ae2b898](https://github.com/continew-org/continew-starter/commit/ae2b898e57ca8e418289a2974c92447ec191e15f))
- 【dependencies】调整 sa-token 版本锁定为 bom 方式PR by iang4yu ([e242568](https://github.com/continew-org/continew-starter/commit/e24256818d716c4c2bbc50d6e7bd0df394bbbd4f))
- 【log】访问日志过滤资源路径 (Gitee#47@dom-w) ([a6a44cd](https://github.com/continew-org/continew-starter/commit/a6a44cd46131d41f8626fe67f6ad9e4d70f1d46c))
- 【log】重构请求和响应信息获取 (Gitee#47@dom-w) ([ca2c886](https://github.com/continew-org/continew-starter/commit/ca2c88651ff84b165ed9c9389fefb346d2be92ab))
- 【log】修复访问日志 JSON 数组打印 (Gitee#47@dom-w) ([199a83f](https://github.com/continew-org/continew-starter/commit/199a83fbea015415484a84b5e7cb535bf804e0bc))
- 删除多余依赖,格式化代码 (Gitee#47@dom-w) ([7e8a15a](https://github.com/continew-org/continew-starter/commit/7e8a15ae8ace650ca5ccf1b807a3149fc2d1e352))
- 优化部分代码及方法命名,移除 SpringWebUtils 部分重复方法 ([f2ba10b](https://github.com/continew-org/continew-starter/commit/f2ba10beae9d17fad7fa260924ede5cd8ca0cbe4))
- 调整 Spring Boot 等依赖为 bom 引入 (Gitee#49@jiang4yu) ([1d4f3a3](https://github.com/continew-org/continew-starter/commit/1d4f3a33b9ea6aa84c096b4d97cbb35220c13d86))
- 【auth/satoken】使用 satoken 官方插件替换 Redisson 缓存 DAO 支持 ([a871048](https://github.com/continew-org/continew-starter/commit/a87104830fa1660035718c4721aa5862433dad7f))
- 【extension/crud】重构删除接口以解决批量删除时 URL 中 ID 列表过长问题 ([45da758](https://github.com/continew-org/continew-starter/commit/45da758dee5446f19a230c47a1f5135a86bbff63))
- 统一配置启用属性描述 ([efaef9d](https://github.com/continew-org/continew-starter/commit/efaef9d7e6a3584e92748e33bf333a2166a3b355))
- 【extension/crud】将 @DictField 注解重命名为 @DictModel,用于更清晰地表示字典结构映射 ([8766f11](https://github.com/continew-org/continew-starter/commit/8766f11eb2ea3c2cedc5754be4d8969298f95cb3))
- 【log】优化日志拦截器配置 ([0463c74](https://github.com/continew-org/continew-starter/commit/0463c74aa4737db69798da00c4f1975401a6659d))
### 🐛 问题修复
- 【data/mp】修改构建本部门及以下数据权限表达式 以支持PostgreSQL (Gitee#48@httpsjt) ([5a2621a](https://github.com/continew-org/continew-starter/commit/5a2621a030b8e4f437e2c37fc4021ee661f497c7))
- 解决部分传递依赖漏洞问题 ([1c65191](https://github.com/continew-org/continew-starter/commit/1c65191b8ab8023fb5990bd532397f3571406420))
### 📦 依赖升级
- sa-token 1.40.0 => 1.41.0 ([a871048](https://github.com/continew-org/continew-starter/commit/a87104830fa1660035718c4721aa5862433dad7f))
## [v2.10.0](https://github.com/continew-org/continew-starter/compare/v2.9.0...v2.10.0) (2025-03-26)
### ✨ 新特性

View File

@@ -13,16 +13,10 @@
<description>ContiNew Starter 认证模块 - SaToken</description>
<dependencies>
<!-- Sa-Token轻量级 Java 权限认证框架,让鉴权变得简单、优雅) -->
<!-- Web 模块 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>
<!-- Sa-Token 整合 JWT -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
</dependency>
<!-- 缓存模块 - Redisson -->
@@ -32,10 +26,34 @@
<optional>true</optional>
</dependency>
<!-- Web 模块 -->
<!-- Sa-Token轻量级 Java 权限认证框架,让鉴权变得简单、优雅) -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Sa-Token 整合 JWT -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
</dependency>
<!-- Sa-Token 集成 Redisson 客户端 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redisson</artifactId>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -30,7 +30,7 @@ import top.continew.starter.auth.satoken.autoconfigure.dao.SaTokenDaoProperties;
public class SaTokenExtensionProperties {
/**
* 是否启用扩展
* 是否启用
*/
private boolean enabled = false;

View File

@@ -18,12 +18,13 @@ package top.continew.starter.auth.satoken.autoconfigure.dao;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import org.redisson.client.RedisClient;
import cn.dev33.satoken.dao.SaTokenDaoForRedisson;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@@ -63,8 +64,8 @@ public class SaTokenDaoConfiguration {
* 自定义持久层实现-Redis默认
*/
@ConditionalOnMissingBean(SaTokenDao.class)
@ConditionalOnClass(RedisClient.class)
@AutoConfigureBefore(RedissonAutoConfiguration.class)
@ConditionalOnBean(RedissonClient.class)
@AutoConfigureAfter(RedissonAutoConfiguration.class)
@ConditionalOnProperty(name = "sa-token.extension.dao.type", havingValue = "redis")
public static class Redis {
static {
@@ -72,8 +73,8 @@ public class SaTokenDaoConfiguration {
}
@Bean
public SaTokenDao saTokenDao() {
return new SaTokenDaoRedisDefaultImpl();
public SaTokenDao saTokenDao(RedissonClient redissonClient) {
return new SaTokenDaoForRedisson(redissonClient);
}
}

View File

@@ -1,147 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.auth.satoken.autoconfigure.dao;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil;
import top.continew.starter.cache.redisson.util.RedisUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 默认 Sa-Token 持久层 Redis 实现参考Sa-Token/sa-token-plugin/sa-token-dao-redisx/SaTokenDaoOfRedis.java
*
* @author Charles7c
* @since 1.0.0
*/
public class SaTokenDaoRedisDefaultImpl implements SaTokenDao {
@Override
public String get(String key) {
return RedisUtils.get(key);
}
@Override
public void set(String key, String value, long timeout) {
if (timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
// 判断是否为永不过期
if (timeout == SaTokenDao.NEVER_EXPIRE) {
RedisUtils.set(key, value);
} else {
RedisUtils.set(key, value, Duration.ofSeconds(timeout));
}
}
@Override
public void update(String key, String value) {
long expire = getTimeout(key);
// -2无此键
if (expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.set(key, value, expire);
}
@Override
public void delete(String key) {
RedisUtils.delete(key);
}
@Override
public long getTimeout(String key) {
long timeout = RedisUtils.getTimeToLive(key);
return timeout < 0 ? timeout : timeout / 1000;
}
@Override
public void updateTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if (timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getTimeout(key);
// 如果其已经被设置为永久,则不作任何处理。如果尚未被设置为永久,那么再次 set 一次
if (expire != SaTokenDao.NEVER_EXPIRE) {
this.set(key, this.get(key), timeout);
}
return;
}
RedisUtils.expire(key, Duration.ofSeconds(timeout));
}
@Override
public Object getObject(String key) {
return RedisUtils.get(key);
}
@Override
public void setObject(String key, Object object, long timeout) {
if (0 == timeout || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
// 判断是否为永不过期
if (timeout == SaTokenDao.NEVER_EXPIRE) {
RedisUtils.set(key, object);
} else {
RedisUtils.set(key, object, Duration.ofSeconds(timeout));
}
}
@Override
public void updateObject(String key, Object object) {
long expire = getObjectTimeout(key);
// -2无此键
if (expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.setObject(key, object, expire);
}
@Override
public void deleteObject(String key) {
RedisUtils.delete(key);
}
@Override
public long getObjectTimeout(String key) {
return this.getTimeout(key);
}
@Override
public void updateObjectTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if (timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getObjectTimeout(key);
// 如果其已经被设置为永久,则不作任何处理。如果尚未被设置为永久,那么再次 set 一次
if (expire != SaTokenDao.NEVER_EXPIRE) {
this.setObject(key, this.getObject(key), timeout);
}
return;
}
RedisUtils.expire(key, Duration.ofSeconds(timeout));
}
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
Collection<String> keys = RedisUtils.keys("%s*%s*".formatted(prefix, keyword));
List<String> list = new ArrayList<>(keys);
return SaFoxUtil.searchList(list, start, size, sortType);
}
}

View File

@@ -0,0 +1,422 @@
<?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>top.continew</groupId>
<artifactId>continew-starter-bom</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<description>ContiNew Starter BOM</description>
<properties>
<revision>2.11.0</revision>
</properties>
<dependencyManagement>
<dependencies>
<!-- 扩展模块 - CRUD - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - CRUD - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - CRUD - MyBatis Flex ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-mf</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 数据权限 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 数据权限 - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 多租户 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 多租户 - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 认证模块 - JustAuth -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-auth-justauth</artifactId>
<version>${revision}</version>
</dependency>
<!-- 认证模块 - SaToken -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-auth-satoken</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据访问模块 - MyBatis Plus -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据访问模块 - MyBatis Flex -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mf</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据访问模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - JetCache -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-jetcache</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - Spring Cache -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-springcache</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<version>${revision}</version>
</dependency>
<!-- 消息模块 - WebSocket -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-messaging-websocket</artifactId>
<version>${revision}</version>
</dependency>
<!-- 消息模块 - 邮件 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-messaging-mail</artifactId>
<version>${revision}</version>
</dependency>
<!-- 验证码模块 - 行为验证码 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-captcha-behavior</artifactId>
<version>${revision}</version>
</dependency>
<!-- 验证码模块 - 图形验证码 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-captcha-graphic</artifactId>
<version>${revision}</version>
</dependency>
<!-- 文件处理模块 - Excel -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-file-excel</artifactId>
<version>${revision}</version>
</dependency>
<!-- 存储模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-storage-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 存储模块 - 本地存储 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-storage-local</artifactId>
<version>${revision}</version>
</dependency>
<!-- 存储模块 - 对象存储 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-storage-oss</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志模块 - 基于拦截器实现Spring Boot Actuator HttpTrace 增强版) -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-log-interceptor</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志模块 - 基于 AOP 实现 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-log-aop</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-log-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 链路追踪模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-trace</artifactId>
<version>${revision}</version>
</dependency>
<!-- 幂等模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-idempotent</artifactId>
<version>${revision}</version>
</dependency>
<!-- 限流模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-ratelimiter</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - XSS 过滤 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-xss</artifactId>
</dependency>
<!-- 安全模块 - 敏感词 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-sensitivewords</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 加密 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-crypto</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 脱敏 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-mask</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 密码编码器 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-password</artifactId>
<version>${revision}</version>
</dependency>
<!-- Web 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
<version>${revision}</version>
</dependency>
<!-- API 文档模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-api-doc</artifactId>
<version>${revision}</version>
</dependency>
<!-- JSON 模块 - Jackson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-json-jackson</artifactId>
<version>${revision}</version>
</dependency>
<!-- 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-core</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- 统一版本号插件 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.7.0</version>
<executions>
<execution>
<!-- Create an effective POM (with versions expanded) for the CLI and documentation -->
<id>flatten-effective-pom</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
<configuration>
<updatePomFile>false</updatePomFile>
<outputDirectory>${project.build.directory}/effective-pom</outputDirectory>
<flattenedPomFilename>continew-starter-dependencies.xml</flattenedPomFilename>
<flattenMode>oss</flattenMode>
<pomElements>
<dependencyManagement>expand</dependencyManagement>
<pluginManagement>expand</pluginManagement>
<properties>remove</properties>
<repositories>remove</repositories>
</pomElements>
</configuration>
</execution>
<execution>
<!-- Flatten and simplify our own POM for install/deploy -->
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>bom</flattenMode>
<pomElements>
<parent>expand</parent>
<properties>keep</properties>
<pluginManagement>keep</pluginManagement>
<repositories>remove</repositories>
</pomElements>
</configuration>
</execution>
<execution>
<id>flatten-clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.4.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<tokenAuth>true</tokenAuth>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<url>https://github.com/continew-org/continew-starter</url>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<developers>
<developer>
<id>charles7c</id>
<name>Charles7c</name>
<email>charles7c@126.com</email>
<roles>
<role>Creator</role>
<role>Java Development Engineer</role>
</roles>
<timezone>+8</timezone>
<url>https://github.com/Charles7c</url>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:continew-org/continew-starter.git</connection>
<developerConnection>scm:git:git@github.com:continew-org/continew-starter.git</developerConnection>
<url>https://github.com/continew-org/continew-starter</url>
</scm>
</project>

View File

@@ -29,6 +29,12 @@
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-redisson</artifactId>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View File

@@ -33,6 +33,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import top.continew.starter.cache.redisson.handler.NameMapperHandler;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.constant.StringConstants;
@@ -104,6 +105,10 @@ public class RedissonAutoConfiguration {
if (CharSequenceUtil.isBlank(clusterServersConfig.getPassword())) {
clusterServersConfig.setPassword(redisProperties.getPassword());
}
// Key 前缀
if (CharSequenceUtil.isNotBlank(properties.getKeyPrefix())) {
clusterServersConfig.setNameMapper(new NameMapperHandler(properties.getKeyPrefix()));
}
}
/**
@@ -130,6 +135,10 @@ public class RedissonAutoConfiguration {
if (CharSequenceUtil.isBlank(sentinelServersConfig.getMasterName())) {
sentinelServersConfig.setMasterName(redisProperties.getSentinel().getMaster());
}
// Key 前缀
if (CharSequenceUtil.isNotBlank(properties.getKeyPrefix())) {
sentinelServersConfig.setNameMapper(new NameMapperHandler(properties.getKeyPrefix()));
}
}
/**
@@ -153,5 +162,9 @@ public class RedissonAutoConfiguration {
singleServerConfig.setAddress(protocolPrefix + redisProperties
.getHost() + StringConstants.COLON + redisProperties.getPort());
}
// Key 前缀
if (CharSequenceUtil.isNotBlank(properties.getKeyPrefix())) {
singleServerConfig.setNameMapper(new NameMapperHandler(properties.getKeyPrefix()));
}
}
}

View File

@@ -33,10 +33,15 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty;
public class RedissonProperties {
/**
* 是否启用 Redisson
* 是否启用
*/
private boolean enabled = true;
/**
* 缓存键前缀
*/
private String keyPrefix;
/**
* Redis 模式
*/
@@ -88,6 +93,14 @@ public class RedissonProperties {
this.enabled = enabled;
}
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
public Mode getMode() {
return mode;
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.cache.redisson.handler;
import cn.hutool.core.text.CharSequenceUtil;
import org.redisson.api.NameMapper;
/**
* 缓存名称映射处理器
*
* @author Charles7c
* @since 2.11.0
*/
public class NameMapperHandler implements NameMapper {
private final String keyPrefix;
public NameMapperHandler(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
@Override
public String map(String name) {
if (CharSequenceUtil.isNotBlank(name) && !name.startsWith(keyPrefix)) {
return keyPrefix + name;
}
return name;
}
@Override
public String unmap(String name) {
if (CharSequenceUtil.isNotBlank(name) && name.startsWith(keyPrefix)) {
return name.substring(keyPrefix.length());
}
return name;
}
}

View File

@@ -34,7 +34,7 @@ import java.awt.*;
public class BehaviorCaptchaProperties {
/**
* 是否启用行为验证码
* 是否启用
*/
private boolean enabled = true;

View File

@@ -30,7 +30,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
public class GraphicCaptchaProperties {
/**
* 是否启用图形验证码
* 是否启用
*/
private boolean enabled = true;

View File

@@ -23,10 +23,10 @@
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!-- AOPAspectJ -->
<!-- Spring AOP Starter -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Hibernate Validator -->

View File

@@ -29,7 +29,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class MyBatisFlexExtensionProperties {
/**
* 是否启用扩展
* 是否启用
*/
private boolean enabled = false;
@@ -57,7 +57,7 @@ public class MyBatisFlexExtensionProperties {
public static class DataPermissionProperties {
/**
* 是否启用数据权限插件
* 是否启用
*/
private boolean enabled = false;
@@ -76,7 +76,7 @@ public class MyBatisFlexExtensionProperties {
public static class PaginationProperties {
/**
* 是否启用分页插件
* 是否启用
*/
private boolean enabled = true;

View File

@@ -31,7 +31,7 @@ import top.continew.starter.data.mp.autoconfigure.idgenerator.MyBatisPlusIdGener
public class MyBatisPlusExtensionProperties {
/**
* 是否启用扩展
* 是否启用
*/
private boolean enabled = false;
@@ -70,7 +70,7 @@ public class MyBatisPlusExtensionProperties {
public static class PaginationProperties {
/**
* 是否启用分页插件
* 是否启用
*/
private boolean enabled = true;

View File

@@ -3,52 +3,22 @@
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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.3.9</version>
<relativePath/>
</parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter-dependencies</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<description>ContiNew Starter 依赖模块</description>
<url>https://github.com/continew-org/continew-starter</url>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>https://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<developers>
<developer>
<id>charles7c</id>
<name>Charles7c</name>
<email>charles7c@126.com</email>
<roles>
<role>Creator</role>
<role>Java Development Engineer</role>
</roles>
<timezone>+8</timezone>
<url>https://github.com/Charles7c</url>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:continew-org/continew-starter.git</connection>
<developerConnection>scm:git:git@github.com:continew-org/continew-starter.git</developerConnection>
<url>https://github.com/continew-org/continew-starter</url>
</scm>
<properties>
<!-- 项目版本号 -->
<revision>2.10.0</revision>
<revision>2.11.0</revision>
<spring-boot.version>3.3.9</spring-boot.version>
<spring-cloud.version>2023.0.5</spring-cloud.version>
<redisson.version>3.45.0</redisson.version>
<jetcache.version>2.7.7</jetcache.version>
<cosid.version>2.11.0</cosid.version>
<sa-token.version>1.40.0</sa-token.version>
<sa-token.version>1.41.0</sa-token.version>
<just-auth.version>1.16.7</just-auth.version>
<mybatis-plus.version>3.5.8</mybatis-plus.version>
<mybatis-flex.version>1.10.8</mybatis-flex.version>
@@ -74,6 +44,10 @@
<ip2region.version>3.3.6</ip2region.version>
<hutool.version>5.8.36</hutool.version>
<snakeyaml.version>2.4</snakeyaml.version>
<!-- 解决部分传递依赖漏洞问题 -->
<commons-beanutils.version>1.9.4</commons-beanutils.version>
<commons-io.version>2.17.0</commons-io.version>
<commons-compress.version>1.26.0</commons-compress.version>
<!-- Maven Plugin Versions -->
<flatten.version>1.7.0</flatten.version>
<spotless.version>2.44.3</spotless.version>
@@ -82,7 +56,16 @@
<dependencyManagement>
<dependencies>
<!-- Spring CloudSpring 团队提供的微服务解决方案) -->
<!-- Spring Boot由 Pivotal 团队提供的全新框架其设计目的是用来简化新Spring应用的初始搭建以及开发过程-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud由 Pivotal 团队提供的微服务解决方案) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
@@ -101,26 +84,10 @@
<!-- JetCache一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新还提供了 Cache 接口用于手工缓存操作) -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-autoconfigure</artifactId>
<artifactId>jetcache-bom</artifactId>
<version>${jetcache.version}</version>
</dependency>
<!-- JetCache 注解 -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-anno</artifactId>
<version>${jetcache.version}</version>
</dependency>
<!-- JetCache Redisson 适配 -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-redisson</artifactId>
<version>${jetcache.version}</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</exclusion>
</exclusions>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- CosId通用、灵活、高性能的分布式 ID 生成器) -->
@@ -143,14 +110,10 @@
<!-- Sa-Token轻量级 Java 权限认证框架,让鉴权变得简单、优雅) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 JWT -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<artifactId>sa-token-bom</artifactId>
<version>${sa-token.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Just Auth开箱即用的整合第三方登录的开源组件脱离繁琐的第三方登录 SDK让登录变得 So easy! -->
@@ -172,6 +135,17 @@
</dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<!-- 注意:
1、此注释在升级到 mybatis-plus 3.5.9 之后的版本时才进行启用,同时删掉后续相对应的mybatis-plus依赖
2、不升级版本可忽略不计,此处仅为了后续升级,做准备
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-bom</artifactId>
<version>${mybatis-plus.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
@@ -191,13 +165,10 @@
<!-- MyBatis FlexMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>${mybatis-flex.version}</version>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-processor</artifactId>
<artifactId>mybatis-flex-dependencies</artifactId>
<version>${mybatis-flex.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Dynamic Datasource基于 Spring Boot 的快速集成多数据源的启动器) -->
@@ -380,266 +351,32 @@
<version>${hutool.version}</version>
</dependency>
<!-- 解决部分传递依赖漏洞问题 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>${commons-compress.version}</version>
</dependency>
<!-- ContiNew Starter 依赖 -->
<!-- 扩展模块 - CRUD - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - CRUD - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - CRUD - MyBatis Flex ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-mf</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 数据权限 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 数据权限 - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 多租户 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 多租户 - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 认证模块 - JustAuth -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-auth-justauth</artifactId>
<version>${revision}</version>
</dependency>
<!-- 认证模块 - SaToken -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-auth-satoken</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据访问模块 - MyBatis Plus -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据访问模块 - MyBatis Flex -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-mf</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据访问模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - JetCache -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-jetcache</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - Spring Cache -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-springcache</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
<version>${revision}</version>
</dependency>
<!-- 消息模块 - WebSocket -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-messaging-websocket</artifactId>
<version>${revision}</version>
</dependency>
<!-- 消息模块 - 邮件 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-messaging-mail</artifactId>
<version>${revision}</version>
</dependency>
<!-- 验证码模块 - 行为验证码 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-captcha-behavior</artifactId>
<version>${revision}</version>
</dependency>
<!-- 验证码模块 - 图形验证码 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-captcha-graphic</artifactId>
<version>${revision}</version>
</dependency>
<!-- 文件处理模块 - Excel -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-file-excel</artifactId>
<version>${revision}</version>
</dependency>
<!-- 存储模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-storage-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 存储模块 - 本地存储 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-storage-local</artifactId>
<version>${revision}</version>
</dependency>
<!-- 存储模块 - 对象存储 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-storage-oss</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志模块 - 基于拦截器实现Spring Boot Actuator HttpTrace 增强版) -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-log-interceptor</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志模块 - 基于 AOP 实现 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-log-aop</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-log-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 链路追踪模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-trace</artifactId>
<version>${revision}</version>
</dependency>
<!-- 幂等模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-idempotent</artifactId>
<version>${revision}</version>
</dependency>
<!-- 限流模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-ratelimiter</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - XSS 过滤 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-xss</artifactId>
</dependency>
<!-- 安全模块 - 敏感词 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-sensitivewords</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 加密 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-crypto</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 脱敏 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-mask</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 密码编码器 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-password</artifactId>
<version>${revision}</version>
</dependency>
<!-- Web 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-web</artifactId>
<version>${revision}</version>
</dependency>
<!-- API 文档模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-api-doc</artifactId>
<version>${revision}</version>
</dependency>
<!-- JSON 模块 - Jackson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-json-jackson</artifactId>
<version>${revision}</version>
</dependency>
<!-- 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-core</artifactId>
<artifactId>continew-starter-bom</artifactId>
<version>${revision}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
@@ -888,4 +625,30 @@
</build>
</profile>
</profiles>
<url>https://github.com/continew-org/continew-starter</url>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>https://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<developers>
<developer>
<id>charles7c</id>
<name>Charles7c</name>
<email>charles7c@126.com</email>
<roles>
<role>Creator</role>
<role>Java Development Engineer</role>
</roles>
<timezone>+8</timezone>
<url>https://github.com/Charles7c</url>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:continew-org/continew-starter.git</connection>
<developerConnection>scm:git:git@github.com:continew-org/continew-starter.git</developerConnection>
<url>https://github.com/continew-org/continew-starter</url>
</scm>
</project>

View File

@@ -19,7 +19,7 @@ package top.continew.starter.extension.crud.annotation;
import java.lang.annotation.*;
/**
* 字典结构字段
* 字典结构映射
*
* @author Charles7c
* @since 2.1.0
@@ -27,7 +27,7 @@ import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DictField {
public @interface DictModel {
/**
* 标签字段名

View File

@@ -30,7 +30,8 @@ import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.handler.CrudApiHandler;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
import top.continew.starter.extension.crud.model.req.IdsReq;
import top.continew.starter.extension.crud.model.resp.IdResp;
import top.continew.starter.extension.crud.model.resp.BasePageResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.extension.crud.validation.CrudValidationGroup;
@@ -123,8 +124,8 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
@Operation(summary = "创建数据", description = "创建数据")
@ResponseBody
@PostMapping
public BaseIdResp<Long> create(@Validated(CrudValidationGroup.Create.class) @RequestBody C req) {
return new BaseIdResp<>(baseService.create(req));
public IdResp<Long> create(@Validated(CrudValidationGroup.Create.class) @RequestBody C req) {
return new IdResp<>(baseService.create(req));
}
/**
@@ -145,15 +146,14 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
/**
* 删除
*
* @param ids ID 列表
* @param req 删除参数
*/
@CrudApi(Api.DELETE)
@Operation(summary = "删除数据", description = "删除数据")
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
@ResponseBody
@DeleteMapping("/{ids}")
public void delete(@PathVariable("ids") List<Long> ids) {
baseService.delete(ids);
@DeleteMapping
public void delete(@Validated @RequestBody IdsReq req) {
baseService.delete(req.getIds());
}
/**

View File

@@ -14,45 +14,33 @@
* limitations under the License.
*/
package top.continew.starter.log.http;
package top.continew.starter.extension.crud.model.req;
import java.util.Map;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 可记录的 HTTP 响应信息
* ID 请求参数
*
* @author Andy WilkinsonSpring Boot Actuator
* @author Charles7c
* @see RecordableHttpRequest
* @since 1.1.0
* @since 2.11.0
*/
public interface RecordableHttpResponse {
public class IdReq implements Serializable {
/**
* 获取状态码
*
* @return 状态码
* ID
*/
int getStatus();
@Schema(description = "ID", example = "1")
@NotNull(message = "ID 不能为空")
private Long id;
/**
* 获取响应头
*
* @return 响应头
*/
Map<String, String> getHeaders();
public Long getId() {
return id;
}
/**
* 获取响应体
*
* @return 响应体
*/
String getBody();
/**
* 获取响应参数
*
* @return 响应参数
*/
Map<String, Object> getParam();
public void setId(Long id) {
this.id = id;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.List;
/**
* ID 列表请求参数
*
* @author Charles7c
* @since 2.11.0
*/
public class IdsReq implements Serializable {
/**
* ID
*/
@Schema(description = "ID", example = "[1,2]")
@NotEmpty(message = "ID 不能为空")
private List<Long> ids;
public List<Long> getIds() {
return ids;
}
public void setIds(List<Long> ids) {
this.ids = ids;
}
}

View File

@@ -26,7 +26,7 @@ import java.io.Serializable;
* @author Charles7c
* @since 2.5.0
*/
public class BaseIdResp<T extends Serializable> implements Serializable {
public class IdResp<T extends Serializable> implements Serializable {
/**
* ID
@@ -42,10 +42,10 @@ public class BaseIdResp<T extends Serializable> implements Serializable {
this.id = id;
}
public BaseIdResp() {
public IdResp() {
}
public BaseIdResp(final T id) {
public IdResp(final T id) {
this.id = id;
}
}

View File

@@ -42,7 +42,8 @@ import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.starter.data.mp.service.impl.ServiceImpl;
import top.continew.starter.data.mp.util.QueryWrapperHelper;
import top.continew.starter.extension.crud.annotation.DictField;
import top.continew.starter.extension.crud.annotation.DictModel;
import top.continew.starter.extension.crud.annotation.DictModel;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
@@ -138,18 +139,18 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
@Override
public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) {
DictField dictField = super.getEntityClass().getDeclaredAnnotation(DictField.class);
CheckUtils.throwIfNull(dictField, "请添加并配置 @DictField 字典结构信息");
DictModel dictModel = super.getEntityClass().getDeclaredAnnotation(DictModel.class);
CheckUtils.throwIfNull(dictModel, "请添加并配置 @DictModel 字典结构信息");
List<L> list = this.list(query, sortQuery);
// 解析映射
List<LabelValueResp> respList = new ArrayList<>(list.size());
String labelKey = dictField.labelKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictField.labelKey(), StringConstants.DOT, true)
: dictField.labelKey();
String valueKey = dictField.valueKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictField.valueKey(), StringConstants.DOT, true)
: dictField.valueKey();
List<String> extraFieldNames = Arrays.stream(dictField.extraKeys())
String labelKey = dictModel.labelKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictModel.labelKey(), StringConstants.DOT, true)
: dictModel.labelKey();
String valueKey = dictModel.valueKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictModel.valueKey(), StringConstants.DOT, true)
: dictModel.valueKey();
List<String> extraFieldNames = Arrays.stream(dictModel.extraKeys())
.map(extraKey -> extraKey.contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(extraKey, StringConstants.DOT, true)
: extraKey)
@@ -165,7 +166,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
continue;
}
// 额外数据
Map<String, Object> extraMap = MapUtil.newHashMap(dictField.extraKeys().length);
Map<String, Object> extraMap = MapUtil.newHashMap(dictModel.extraKeys().length);
for (String extraFieldName : extraFieldNames) {
extraMap.put(extraFieldName, ReflectUtil.getFieldValue(entity, extraFieldName));
}

View File

@@ -29,7 +29,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
public class DataPermissionProperties {
/**
* 是否启用多租户
* 是否启用
*/
private boolean enabled = true;

View File

@@ -24,5 +24,11 @@
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-core</artifactId>
</dependency>
<!-- ContiNew Starter 数据访问模块 - 核心模块-->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -16,36 +16,45 @@
package top.continew.starter.extension.datapermission.handler;
import cn.hutool.core.text.CharSequenceUtil;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.data.core.enums.DatabaseType;
import top.continew.starter.data.core.util.MetaUtils;
import top.continew.starter.extension.datapermission.annotation.DataPermission;
import top.continew.starter.extension.datapermission.enums.DataScope;
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
import top.continew.starter.extension.datapermission.model.RoleContext;
import top.continew.starter.extension.datapermission.model.UserContext;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
/**
* 默认数据权限处理器
*
@@ -57,6 +66,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
private static final Logger log = LoggerFactory.getLogger(DefaultDataPermissionHandler.class);
private final DataPermissionUserContextProvider dataPermissionUserContextProvider;
private static final DataSource dataSource = SpringUtil.getBean(DataSource.class);
public DefaultDataPermissionHandler(DataPermissionUserContextProvider dataPermissionUserContextProvider) {
this.dataPermissionUserContextProvider = dataPermissionUserContextProvider;
@@ -133,13 +143,34 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectItem<>(new Column(dataPermission.id()))));
select.setFromItem(new Table(dataPermission.deptTableAlias()));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataPermission.id()));
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
Function function = new Function();
function.setName("find_in_set");
function.setParameters(new ExpressionList<>(new LongValue(userContext.getDeptId()), new Column("ancestors")));
select.setWhere(new OrExpression(equalsTo, function));
DatabaseType databaseType = MetaUtils.getDatabaseType(dataSource);
Expression inSetExpression;
if (DatabaseType.MYSQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
Function findInSetFunction = new Function();
findInSetFunction.setName("find_in_set");
findInSetFunction.setParameters(new ExpressionList<>(new LongValue(userContext
.getDeptId()), new StringValue(new Column("ancestors") + ",")));
inSetExpression = findInSetFunction;
} else if (DatabaseType.POSTGRE_SQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
// 构建 concat 函数
Function concatFunction = new Function("concat");
concatFunction.setParameters(new ExpressionList<>(new Column("ancestors"), new StringValue(",")));
// 创建 LIKE 函数
LikeExpression likeExpression = new LikeExpression();
likeExpression.setLeftExpression(concatFunction);
likeExpression.setRightExpression(new StringValue("%," + userContext.getDeptId() + ",%"));
inSetExpression = likeExpression;
} else {
throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(""));
}
select.setWhere(new OrExpression(equalsTo, inSetExpression));
subSelect.setSelect(select);
// 构建父查询
InExpression inExpression = new InExpression();

View File

@@ -32,7 +32,7 @@ import java.util.List;
public class TenantProperties {
/**
* 是否启用多租户
* 是否启用
*/
private boolean enabled = true;

View File

@@ -13,11 +13,6 @@
<description>ContiNew Starter 幂等模块</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.continew</groupId>

View File

@@ -0,0 +1,206 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.json.jackson.util;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* json 构建工具
*
* @author echo
* @since 2.11.0
*/
public class JSONBuilder {
private static final ObjectMapper OBJECT_MAPPER = SpringUtil.getBean(ObjectMapper.class);
private final ObjectNode rootNode;
private JSONBuilder() {
this.rootNode = OBJECT_MAPPER.createObjectNode();
}
/**
* 开始构建
*
* @return {@link JSONBuilder }
*/
public static JSONBuilder builder() {
return new JSONBuilder();
}
/**
* 添加 字符串
*
* @param key key 值
* @param value 值
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, String value) {
Objects.requireNonNull(key, "键不能为 null");
if (value != null) {
rootNode.put(key, value);
}
return this;
}
/**
* 添加 int
*
* @param key key 值
* @param value 值
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, int value) {
Objects.requireNonNull(key, "键不能为 null");
rootNode.put(key, value);
return this;
}
/**
* 添加 long
*
* @param key key 值
* @param value 值
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, long value) {
Objects.requireNonNull(key, "键不能为 null");
rootNode.put(key, value);
return this;
}
/**
* 添加 布尔
*
* @param key key 值
* @param value 值
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, boolean value) {
Objects.requireNonNull(key, "键不能为 null");
rootNode.put(key, value);
return this;
}
/**
* 添加 浮点
*
* @param key key 值
* @param value 值
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, double value) {
Objects.requireNonNull(key, "键不能为 null");
rootNode.put(key, value);
return this;
}
/**
* 添加 json
*
* @param key key 值
* @param value 值
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, JsonNode value) {
Objects.requireNonNull(key, "键不能为 null");
if (value != null) {
rootNode.set(key, value);
}
return this;
}
/**
* 添加 Object
*
* @param key key 值
* @param value 值
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, Object value) {
Objects.requireNonNull(key, "键不能为 null");
if (value != null) {
rootNode.set(key, OBJECT_MAPPER.valueToTree(value));
}
return this;
}
/**
* 添加 List 到 JSON
*
* @param key key 值
* @param list list 参数
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, List<?> list) {
Objects.requireNonNull(key, "键不能为 null");
if (list != null) {
ArrayNode arrayNode = OBJECT_MAPPER.createArrayNode();
for (Object item : list) {
arrayNode.add(OBJECT_MAPPER.valueToTree(item));
}
rootNode.set(key, arrayNode);
}
return this;
}
/**
* 添加 Map 到 JSON
*
* @param key key 值
* @param map map 参数
* @return {@link JSONBuilder }
*/
public JSONBuilder add(String key, Map<?, ?> map) {
Objects.requireNonNull(key, "键不能为 null");
if (map != null) {
ObjectNode objectNode = OBJECT_MAPPER.valueToTree(map);
rootNode.set(key, objectNode);
}
return this;
}
/**
* 构建
*
* @return {@link JsonNode }
*/
public JsonNode build() {
return rootNode;
}
/**
* 构建 json 字符串
*
* @return {@link String }
*/
public String buildString() {
try {
return rootNode.toString();
} catch (Exception e) {
throw new RuntimeException("构建 JSON 字符串失败", e);
}
}
}

View File

@@ -0,0 +1,237 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.json.jackson.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* json 工具
*
* @author echo
* @since 2.11.0
*/
public class JSONUtils {
private JSONUtils() {
}
/**
* Jackson 对象映射器,用于 JSON 解析与序列化。
*/
private static final ObjectMapper OBJECT_MAPPER = SpringUtil.getBean(ObjectMapper.class);
/**
* 获取 Jackson 对象映射器。
*
* @return {@link ObjectMapper} Jackson 对象映射器
*/
public static ObjectMapper getObjectMapper() {
return OBJECT_MAPPER;
}
/**
* 对象转为 json 字符串
*
* @param object 对象
* @return {@link String }
*/
public static String toJsonStr(Object object) {
if (ObjectUtil.isNull(object)) {
return null;
}
try {
return OBJECT_MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* 将对象转换为 JsonNode。
*
* @param obj 需要转换的对象
* @return 转换后的 {@link JsonNode},如果 obj 为空,则返回 null
*/
public static JsonNode toJson(Object obj) {
if (obj == null) {
return null;
}
try {
return OBJECT_MAPPER.valueToTree(obj);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 将 List 转换为 JsonNode。
*
* @param list 输入的 List
* @return 转换后的 {@link JsonNode}
*/
public static JsonNode listToJson(List<?> list) {
return toJson(list);
}
/**
* 将 Map 转换为 JsonNode。
*
* @param map 输入的 Map
* @return 转换后的 {@link JsonNode}
*/
public static JsonNode mapToJson(Map<?, ?> map) {
return toJson(map);
}
/**
* 将 JsonNode 转换为 List<String>,用于环境变量格式解析。
*
* @param jsonNode 需要转换的 JsonNode
* @return 转换后的 List<String>
*/
public static List<String> jsonToEnvList(JsonNode jsonNode) {
if (jsonNode == null || jsonNode.isNull()) {
return new ArrayList<>();
}
List<String> envList = new ArrayList<>();
jsonNode.fields().forEachRemaining(field -> {
String key = field.getKey();
JsonNode valueNode = field.getValue();
String value = valueNode.isValueNode() ? valueNode.asText() : valueNode.toString();
envList.add(key + "=" + value);
});
return envList;
}
/**
* 将 JsonNode 转换为 List<String>。
*
* @param jsonNode 需要转换的 JsonNode
* @return 转换后的 List<String>
*/
public static List<String> jsonToStringList(JsonNode jsonNode) {
if (jsonNode == null || jsonNode.isNull()) {
return new ArrayList<>();
}
try {
return OBJECT_MAPPER.convertValue(jsonNode, new TypeReference<>() {
});
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 将 JsonNode 转换为指定类型的 Java 对象。
*
* @param jsonNode JSON 数据
* @param clazz 目标 Java 类
* @return 解析后的 Java 对象
*/
public static <T> T fromJson(JsonNode jsonNode, Class<T> clazz) {
try {
return OBJECT_MAPPER.treeToValue(jsonNode, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* 解析 JSON 字符串为 Java 对象。
*
* @param str JSON 字符串
* @param clazz 目标 Java 类
* @return 解析后的 Java 对象
*/
public static <T> T parseObject(String str, Class<T> clazz) {
if (StrUtil.isEmpty(str)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(str, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 字符串 解析为 list<T>
*
* @param str 字符串
* @param clazz 目标 Java 类
* @return 解析后的 List<T>
*/
public static <T> List<T> parseArray(String str, Class<T> clazz) {
if (StrUtil.isEmpty(str)) {
return new ArrayList<>();
}
try {
return OBJECT_MAPPER.readValue(str, OBJECT_MAPPER.getTypeFactory()
.constructCollectionType(List.class, clazz));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 判断字符串是否为 JSON 格式。
*
* @param str 字符串
* @return 是否为 JSON 格式
*/
public static boolean isTypeJSON(String str) {
if (StrUtil.isEmpty(str)) {
return false;
}
try {
OBJECT_MAPPER.readTree(str);
return true;
} catch (IOException e) {
return false;
}
}
/**
* 将 JSON 字符串转换为指定类型的 Java 对象。
*
* @param str 字符串
* @param clazz 目标对象的 Class 类型
* @return 解析后的 Java 对象
*/
public static <T> T toBean(String str, Class<T> clazz) {
if (StrUtil.isEmpty(str)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(str, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -16,8 +16,6 @@
package top.continew.starter.log.aspect;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@@ -25,8 +23,6 @@ import org.aspectj.lang.annotation.Pointcut;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.continew.starter.log.handler.LogHandler;
import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
import top.continew.starter.log.model.AccessLogContext;
import top.continew.starter.log.model.LogProperties;
@@ -107,22 +103,16 @@ public class AccessLogAspect {
if (attributes == null) {
return joinPoint.proceed();
}
HttpServletRequest request = attributes.getRequest();
HttpServletResponse response = attributes.getResponse();
try {
// 开始访问日志记录
logHandler.accessLogStart(AccessLogContext.builder()
.startTime(startTime)
.request(new RecordableServletHttpRequest(request))
.properties(logProperties)
.build());
return joinPoint.proceed();
} finally {
Instant endTime = Instant.now();
logHandler.accessLogFinish(AccessLogContext.builder()
.endTime(endTime)
.response(new RecordableServletHttpResponse(response, response.getStatus()))
.build());
logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build());
}
}
}

View File

@@ -18,8 +18,6 @@ package top.continew.starter.log.aspect;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.text.CharSequenceUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@@ -35,7 +33,6 @@ import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.handler.LogHandler;
import top.continew.starter.log.model.LogProperties;
import top.continew.starter.log.model.LogRecord;
import top.continew.starter.web.util.SpringWebUtils;
import java.lang.reflect.Method;
import java.time.Instant;
@@ -79,7 +76,6 @@ public class LogAspect {
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Instant startTime = Instant.now();
// 指定规则不记录
HttpServletRequest request = SpringWebUtils.getRequest();
Method targetMethod = this.getMethod(joinPoint);
Class<?> targetClass = joinPoint.getTarget().getClass();
if (!isRequestRecord(targetMethod, targetClass)) {
@@ -87,7 +83,7 @@ public class LogAspect {
}
String errorMsg = null;
// 开始记录
LogRecord.Started startedLogRecord = logHandler.start(startTime, request);
LogRecord.Started startedLogRecord = logHandler.start(startTime);
try {
// 执行目标方法
return joinPoint.proceed();
@@ -97,8 +93,7 @@ public class LogAspect {
} finally {
try {
Instant endTime = Instant.now();
HttpServletResponse response = SpringWebUtils.getResponse();
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, logProperties
.getIncludes(), targetMethod, targetClass);
// 记录异常信息
if (errorMsg != null) {

View File

@@ -22,20 +22,15 @@ import cn.hutool.core.util.ObjectUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.log.annotation.Log;
import top.continew.starter.log.enums.Include;
import top.continew.starter.log.http.RecordableHttpRequest;
import top.continew.starter.log.http.RecordableHttpResponse;
import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
import top.continew.starter.log.model.AccessLogContext;
import top.continew.starter.log.model.AccessLogProperties;
import top.continew.starter.log.model.LogRecord;
import top.continew.starter.log.util.AccessLogUtils;
import top.continew.starter.web.util.ServletUtils;
import java.lang.reflect.Method;
import java.time.Duration;
@@ -55,19 +50,18 @@ public abstract class AbstractLogHandler implements LogHandler {
private final TransmittableThreadLocal<AccessLogContext> logContextThread = new TransmittableThreadLocal<>();
@Override
public LogRecord.Started start(Instant startTime, HttpServletRequest request) {
return LogRecord.start(startTime, new RecordableServletHttpRequest(request));
public LogRecord.Started start(Instant startTime) {
return LogRecord.start(startTime);
}
@Override
public LogRecord finish(LogRecord.Started started,
Instant endTime,
HttpServletResponse response,
Set<Include> includes,
Method targetMethod,
Class<?> targetClass) {
Set<Include> includeSet = this.getIncludes(includes, targetMethod, targetClass);
LogRecord logRecord = this.finish(started, endTime, response, includeSet);
LogRecord logRecord = this.finish(started, endTime, includeSet);
// 记录日志描述
if (includeSet.contains(Include.DESCRIPTION)) {
this.logDescription(logRecord, targetMethod);
@@ -80,11 +74,8 @@ public abstract class AbstractLogHandler implements LogHandler {
}
@Override
public LogRecord finish(LogRecord.Started started,
Instant endTime,
HttpServletResponse response,
Set<Include> includes) {
return started.finish(endTime, new RecordableServletHttpResponse(response, response.getStatus()), includes);
public LogRecord finish(LogRecord.Started started, Instant endTime, Set<Include> includes) {
return started.finish(endTime, includes);
}
/**
@@ -174,16 +165,15 @@ public abstract class AbstractLogHandler implements LogHandler {
public void accessLogStart(AccessLogContext accessLogContext) {
AccessLogProperties properties = accessLogContext.getProperties().getAccessLog();
// 是否需要打印 规则: 是否打印开关 或 放行路径
if (!properties.isEnabled() || accessLogContext.getProperties()
.isMatch(accessLogContext.getRequest().getPath())) {
if (!properties.isEnabled() || AccessLogUtils.exclusionPath(accessLogContext.getProperties(), ServletUtils
.getRequestPath())) {
return;
}
// 构建上下文
logContextThread.set(accessLogContext);
RecordableHttpRequest request = accessLogContext.getRequest();
String path = request.getPath();
String param = AccessLogUtils.getParam(request, properties);
log.info(param != null ? "[Start] [{}] {} param: {}" : "[Start] [{}] {}", request.getMethod(), path, param);
String param = AccessLogUtils.getParam(properties);
log.info(param != null ? "[Start] [{}] {} param: {}" : "[Start] [{}] {}", ServletUtils
.getRequestMethod(), ServletUtils.getRequestPath(), param);
}
@Override
@@ -193,11 +183,9 @@ public abstract class AbstractLogHandler implements LogHandler {
return;
}
try {
RecordableHttpRequest request = logContext.getRequest();
RecordableHttpResponse response = accessLogContext.getResponse();
Duration timeTaken = Duration.between(logContext.getStartTime(), accessLogContext.getEndTime());
log.info("[End] [{}] {} {} {}ms", request.getMethod(), request.getPath(), response.getStatus(), timeTaken
.toMillis());
log.info("[End] [{}] {} {} {}ms", ServletUtils.getRequestMethod(), ServletUtils
.getRequestPath(), ServletUtils.getResponseStatus(), timeTaken.toMillis());
} finally {
logContextThread.remove();
}

View File

@@ -16,8 +16,6 @@
package top.continew.starter.log.handler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import top.continew.starter.log.enums.Include;
import top.continew.starter.log.model.AccessLogContext;
import top.continew.starter.log.model.LogRecord;
@@ -39,28 +37,25 @@ public interface LogHandler {
* 开始日志记录
*
* @param startTime 开始时间
* @param request 请求对象
* @return 日志记录器
*/
LogRecord.Started start(Instant startTime, HttpServletRequest request);
LogRecord.Started start(Instant startTime);
/**
* 结束日志记录
*
* @param started 开始日志记录器
* @param endTime 结束时间
* @param response 响应对象
* @param includes 包含信息
* @return 日志记录
*/
LogRecord finish(LogRecord.Started started, Instant endTime, HttpServletResponse response, Set<Include> includes);
LogRecord finish(LogRecord.Started started, Instant endTime, Set<Include> includes);
/**
* 结束日志记录
*
* @param started 开始日志记录器-
* @param endTime 结束时间
* @param response 响应对象
* @param includes 包含信息
* @param targetMethod 目标方法
* @param targetClass 目标类
@@ -68,7 +63,6 @@ public interface LogHandler {
*/
LogRecord finish(LogRecord.Started started,
Instant endTime,
HttpServletResponse response,
Set<Include> includes,
Method targetMethod,
Class<?> targetClass);

View File

@@ -1,84 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.log.http;
import java.net.URI;
import java.util.Map;
/**
* 可记录的 HTTP 请求信息
*
* @author Andy WilkinsonSpring Boot Actuator
* @author Phillip WebbSpring Boot Actuator
* @author Charles7c
* @author echo
* @see RecordableHttpResponse
* @since 1.1.0
*/
public interface RecordableHttpRequest {
/**
* 获取请求方式
*
* @return 请求方式
*/
String getMethod();
/**
* 获取 URL
*
* @return URL
*/
URI getUrl();
/**
* 获取路径
* <p>/foo/bar</p>
*
* @return 路径
* @since 2.10.0
*/
String getPath();
/**
* 获取请求头
*
* @return 请求头
*/
Map<String, String> getHeaders();
/**
* 获取请求体
*
* @return 请求体
*/
String getBody();
/**
* 获取请求参数
*
* @return 请求参数
*/
Map<String, Object> getParam();
/**
* 获取 IP
*
* @return IP
*/
String getIp();
}

View File

@@ -1,108 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.log.http.servlet;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.util.UriUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.log.http.RecordableHttpRequest;
import top.continew.starter.web.util.RepeatReadRequestWrapper;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
/**
* 可记录的 HTTP 请求信息适配器
*
* @author Andy WilkinsonSpring Boot Actuator
* @author Charles7c
* @author echo
* @since 1.1.0
*/
public final class RecordableServletHttpRequest implements RecordableHttpRequest {
private final HttpServletRequest request;
public RecordableServletHttpRequest(HttpServletRequest request) {
this.request = request;
}
@Override
public String getMethod() {
return request.getMethod();
}
@Override
public URI getUrl() {
String queryString = request.getQueryString();
if (CharSequenceUtil.isBlank(queryString)) {
return URI.create(request.getRequestURL().toString());
}
try {
StringBuilder urlBuilder = this.appendQueryString(queryString);
return new URI(urlBuilder.toString());
} catch (URISyntaxException e) {
String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8);
StringBuilder urlBuilder = this.appendQueryString(encoded);
return URI.create(urlBuilder.toString());
}
}
@Override
public String getPath() {
return request.getRequestURI();
}
@Override
public Map<String, String> getHeaders() {
return JakartaServletUtil.getHeaderMap(request);
}
@Override
public String getBody() {
if (request instanceof RepeatReadRequestWrapper wrapper && !wrapper.isMultipartContent(request)) {
String body = JakartaServletUtil.getBody(request);
return JSONUtil.isTypeJSON(body) ? body : null;
}
return null;
}
@Override
public Map<String, Object> getParam() {
String body = this.getBody();
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
? JSONUtil.toBean(body, Map.class)
: Collections.unmodifiableMap(JakartaServletUtil.getParamMap(request));
}
@Override
public String getIp() {
return JakartaServletUtil.getClientIP(request);
}
private StringBuilder appendQueryString(String queryString) {
return new StringBuilder().append(request.getRequestURL())
.append(StringConstants.QUESTION_MARK)
.append(queryString);
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.log.http.servlet;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletResponse;
import top.continew.starter.web.util.ServletUtils;
import top.continew.starter.web.util.RepeatReadResponseWrapper;
import top.continew.starter.log.http.RecordableHttpResponse;
import java.util.Map;
/**
* 可记录的 HTTP 响应信息适配器
*
* @author Andy WilkinsonSpring Boot Actuator
* @author Charles7c
* @author echo
* @since 1.1.0
*/
public final class RecordableServletHttpResponse implements RecordableHttpResponse {
private final HttpServletResponse response;
private final int status;
public RecordableServletHttpResponse(HttpServletResponse response, int status) {
this.response = response;
this.status = status;
}
@Override
public int getStatus() {
return this.status;
}
@Override
public Map<String, String> getHeaders() {
return ServletUtils.getHeaderMap(response);
}
@Override
public String getBody() {
if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) {
String body = wrapper.getResponseContent();
return JSONUtil.isTypeJSON(body) ? body : null;
}
return null;
}
@Override
public Map<String, Object> getParam() {
String body = this.getBody();
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null;
}
}

View File

@@ -16,9 +16,6 @@
package top.continew.starter.log.model;
import top.continew.starter.log.http.RecordableHttpRequest;
import top.continew.starter.log.http.RecordableHttpResponse;
import java.time.Instant;
/**
@@ -39,16 +36,6 @@ public class AccessLogContext {
*/
private Instant endTime;
/**
* 请求信息
*/
private final RecordableHttpRequest request;
/**
* 响应信息
*/
private final RecordableHttpResponse response;
/**
* 配置信息
*/
@@ -57,8 +44,6 @@ public class AccessLogContext {
private AccessLogContext(Builder builder) {
this.startTime = builder.startTime;
this.endTime = builder.endTime;
this.request = builder.request;
this.response = builder.response;
this.properties = builder.properties;
}
@@ -70,14 +55,6 @@ public class AccessLogContext {
return endTime;
}
public RecordableHttpRequest getRequest() {
return request;
}
public RecordableHttpResponse getResponse() {
return response;
}
public LogProperties getProperties() {
return properties;
}
@@ -97,8 +74,6 @@ public class AccessLogContext {
private Instant startTime;
private Instant endTime;
private RecordableHttpRequest request;
private RecordableHttpResponse response;
private LogProperties properties;
private Builder() {
@@ -114,16 +89,6 @@ public class AccessLogContext {
return this;
}
public Builder request(RecordableHttpRequest request) {
this.request = request;
return this;
}
public Builder response(RecordableHttpResponse response) {
this.response = response;
return this;
}
public Builder properties(LogProperties properties) {
this.properties = properties;
return this;

View File

@@ -29,7 +29,7 @@ import java.util.List;
public class AccessLogProperties {
/**
* 是否打印访问日志(类似于 Nginx access log
* 是否启用
* <p>
* 不记录请求日志也支持开启打印访问日志
* </p>

View File

@@ -36,7 +36,7 @@ import java.util.Set;
public class LogProperties {
/**
* 是否启用日志
* 是否启用
*/
private boolean enabled = true;

View File

@@ -17,8 +17,6 @@
package top.continew.starter.log.model;
import top.continew.starter.log.enums.Include;
import top.continew.starter.log.http.RecordableHttpRequest;
import top.continew.starter.log.http.RecordableHttpResponse;
import java.time.Duration;
import java.time.Instant;
@@ -80,22 +78,20 @@ public class LogRecord {
/**
* 开始记录日志
*
* @param request 请求信息
* @return 日志记录器
*/
public static Started start(RecordableHttpRequest request) {
return start(Instant.now(), request);
public static Started start() {
return start(Instant.now());
}
/**
* 开始记录日志
*
* @param timestamp 开始时间
* @param request 请求信息
* @return 日志记录器
*/
public static Started start(Instant timestamp, RecordableHttpRequest request) {
return new Started(timestamp, request);
public static Started start(Instant timestamp) {
return new Started(timestamp);
}
/**
@@ -105,24 +101,20 @@ public class LogRecord {
private final Instant timestamp;
private final RecordableHttpRequest request;
private Started(Instant timestamp, RecordableHttpRequest request) {
private Started(Instant timestamp) {
this.timestamp = timestamp;
this.request = request;
}
/**
* 结束日志记录
*
* @param timestamp 结束时间
* @param response 响应信息
* @param includes 包含信息
* @return 日志记录
*/
public LogRecord finish(Instant timestamp, RecordableHttpResponse response, Set<Include> includes) {
LogRequest logRequest = new LogRequest(this.request, includes);
LogResponse logResponse = new LogResponse(response, includes);
public LogRecord finish(Instant timestamp, Set<Include> includes) {
LogRequest logRequest = new LogRequest(includes);
LogResponse logResponse = new LogResponse(includes);
Duration duration = Duration.between(this.timestamp, timestamp);
return new LogRecord(this.timestamp, logRequest, logResponse, duration);
}

View File

@@ -22,7 +22,6 @@ import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.IpUtils;
import top.continew.starter.web.util.ServletUtils;
import top.continew.starter.log.enums.Include;
import top.continew.starter.log.http.RecordableHttpRequest;
import java.net.URI;
import java.util.Map;
@@ -81,15 +80,15 @@ public class LogRequest {
*/
private String os;
public LogRequest(RecordableHttpRequest request, Set<Include> includes) {
this.method = request.getMethod();
this.url = request.getUrl();
this.ip = request.getIp();
this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? request.getHeaders() : null;
public LogRequest(Set<Include> includes) {
this.method = ServletUtils.getRequestMethod();
this.url = ServletUtils.getRequestUrl();
this.ip = ServletUtils.getRequestIp();
this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? ServletUtils.getRequestHeaders() : null;
if (includes.contains(Include.REQUEST_BODY)) {
this.body = request.getBody();
this.body = ServletUtils.getRequestBody();
} else if (includes.contains(Include.REQUEST_PARAM)) {
this.param = request.getParam();
this.param = ServletUtils.getRequestParams();
}
this.address = (includes.contains(Include.IP_ADDRESS))
? ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip))

View File

@@ -17,7 +17,7 @@
package top.continew.starter.log.model;
import top.continew.starter.log.enums.Include;
import top.continew.starter.log.http.RecordableHttpResponse;
import top.continew.starter.web.util.ServletUtils;
import java.util.Map;
import java.util.Set;
@@ -50,13 +50,13 @@ public class LogResponse {
*/
private Map<String, Object> param;
public LogResponse(RecordableHttpResponse response, Set<Include> includes) {
this.status = response.getStatus();
this.headers = (includes.contains(Include.RESPONSE_HEADERS)) ? response.getHeaders() : null;
public LogResponse(Set<Include> includes) {
this.status = ServletUtils.getResponseStatus();
this.headers = (includes.contains(Include.RESPONSE_HEADERS)) ? ServletUtils.getResponseHeaders() : null;
if (includes.contains(Include.RESPONSE_BODY)) {
this.body = response.getBody();
this.body = ServletUtils.getResponseBody();
} else if (includes.contains(Include.RESPONSE_PARAM)) {
this.param = response.getParam();
this.param = ServletUtils.getResponseParams();
}
}

View File

@@ -16,14 +16,17 @@
package top.continew.starter.log.util;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import top.continew.starter.log.http.RecordableHttpRequest;
import com.fasterxml.jackson.databind.JsonNode;
import top.continew.starter.json.jackson.util.JSONUtils;
import top.continew.starter.log.model.AccessLogProperties;
import top.continew.starter.log.model.LogProperties;
import top.continew.starter.web.util.ServletUtils;
import top.continew.starter.web.util.SpringWebUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
* 访问日志工具类
@@ -34,42 +37,81 @@ import java.util.Map;
*/
public class AccessLogUtils {
private AccessLogUtils() {
}
/**
* 资源路径 - doc 路径
*/
private static final List<String> RESOURCE_PATH = List
.of("/doc/**", "/v2/api-docs/**", "/v3/api-docs/**", "/webjars/**", "/swagger-resources/**", "/swagger-ui.html");
/**
* 获取参数信息
*
* @param request 请求
* @param properties 属性
* @return {@link String }
*/
public static String getParam(RecordableHttpRequest request, AccessLogProperties properties) {
public static String getParam(AccessLogProperties properties) {
// 是否需要打印请求参数
if (!properties.isPrintRequestParam()) {
return null;
}
// 参数为空返回空
Map<String, Object> params;
Object params;
try {
params = request.getParam();
params = getAccessLogReqParam();
} catch (Exception e) {
return null;
}
if (ObjectUtil.isEmpty(params) || params.isEmpty()) {
if (ObjectUtil.isEmpty(params)) {
return null;
}
// 是否需要对特定入参脱敏
if (properties.isParamSensitive()) {
params = filterSensitiveParams(params, properties.getSensitiveParams());
params = processSensitiveParams(params, properties.getSensitiveParams());
}
// 是否自动截断超长参数值
if (properties.isLongParamTruncate()) {
params = truncateLongParams(params, properties.getLongParamThreshold(), properties
params = processTruncateLongParams(params, properties.getLongParamThreshold(), properties
.getLongParamMaxLength(), properties.getLongParamSuffix());
}
return JSONUtil.toJsonStr(params);
return JSONUtils.toJsonStr(params);
}
/**
* 排除路径
*
* @param properties 属性
* @param path 路径
* @return boolean
*/
public static boolean exclusionPath(LogProperties properties, String path) {
// 放行路由配置的排除检查
return properties.isMatch(path) || RESOURCE_PATH.stream()
.anyMatch(resourcePath -> SpringWebUtils.isMatch(path, resourcePath));
}
/**
* 处理敏感参数,支持 Map 和 List<Map<String, Object>> 类型
*
* @param params 参数
* @param sensitiveParams 敏感参数列表
* @return 处理后的参数
*/
private static Object processSensitiveParams(Object params, List<String> sensitiveParams) {
if (params instanceof Map) {
return filterSensitiveParams((Map<String, Object>)params, sensitiveParams);
} else if (params instanceof List) {
return ((List<?>)params).stream()
.filter(item -> item instanceof Map)
.map(item -> filterSensitiveParams((Map<String, Object>)item, sensitiveParams))
.collect(Collectors.toList());
}
return params;
}
/**
@@ -86,11 +128,34 @@ public class AccessLogUtils {
Map<String, Object> filteredParams = new HashMap<>(params);
for (String sensitiveKey : sensitiveParams) {
filteredParams.computeIfPresent(sensitiveKey, (key, value) -> "***");
if (filteredParams.containsKey(sensitiveKey)) {
filteredParams.put(sensitiveKey, "***");
}
}
return filteredParams;
}
/**
* 处理超长参数,支持 Map 和 List<Map<String, Object>> 类型
*
* @param params 参数
* @param threshold 截断阈值(值长度超过该值才截断)
* @param maxLength 最大长度
* @param suffix 后缀(如 "..."
* @return 处理后的参数
*/
private static Object processTruncateLongParams(Object params, int threshold, int maxLength, String suffix) {
if (params instanceof Map) {
return truncateLongParams((Map<String, Object>)params, threshold, maxLength, suffix);
} else if (params instanceof List) {
return ((List<?>)params).stream()
.filter(Map.class::isInstance)
.map(item -> truncateLongParams((Map<String, Object>)item, threshold, maxLength, suffix))
.collect(Collectors.toList());
}
return params;
}
/**
* 截断超长参数
*
@@ -120,6 +185,25 @@ public class AccessLogUtils {
return truncatedParams;
}
private AccessLogUtils() {
/**
* 获取访问日志请求参数
*
* @return {@link Object }
*/
private static Object getAccessLogReqParam() {
String body = ServletUtils.getRequestBody();
if (CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body)) {
try {
JsonNode jsonNode = JSONUtils.getObjectMapper().readTree(body);
if (jsonNode.isArray()) {
return JSONUtils.toBean(body, List.class);
} else {
return JSONUtils.toBean(body, Map.class);
}
} catch (Exception e) {
return null;
}
}
return Collections.unmodifiableMap(ServletUtils.getRequestParams());
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.log.annotation.ConditionalOnEnabledLog;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.dao.impl.DefaultLogDaoImpl;
@@ -56,7 +57,9 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor(logProperties, logHandler(), logDao()));
registry.addInterceptor(new LogInterceptor(logProperties, logHandler(), logDao()))
.addPathPatterns(StringConstants.PATH_PATTERN)
.excludePathPatterns(logProperties.getExcludePatterns());
}
/**

View File

@@ -27,12 +27,10 @@ import org.springframework.lang.NonNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import top.continew.starter.log.annotation.Log;
import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
import top.continew.starter.log.model.AccessLogContext;
import top.continew.starter.log.model.LogProperties;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.handler.LogHandler;
import top.continew.starter.log.model.AccessLogContext;
import top.continew.starter.log.model.LogProperties;
import top.continew.starter.log.model.LogRecord;
import java.lang.reflect.Method;
@@ -64,14 +62,10 @@ public class LogInterceptor implements HandlerInterceptor {
@NonNull HttpServletResponse response,
@NonNull Object handler) {
Instant startTime = Instant.now();
logHandler.accessLogStart(AccessLogContext.builder()
.startTime(startTime)
.request(new RecordableServletHttpRequest(request))
.properties(logProperties)
.build());
logHandler.accessLogStart(AccessLogContext.builder().startTime(startTime).properties(logProperties).build());
// 开始日志记录
if (this.isRequestRecord(handler, request)) {
LogRecord.Started startedLogRecord = logHandler.start(startTime, request);
LogRecord.Started startedLogRecord = logHandler.start(startTime);
logTtl.set(startedLogRecord);
}
return true;
@@ -84,10 +78,7 @@ public class LogInterceptor implements HandlerInterceptor {
Exception e) {
try {
Instant endTime = Instant.now();
logHandler.accessLogFinish(AccessLogContext.builder()
.endTime(endTime)
.response(new RecordableServletHttpResponse(response, response.getStatus()))
.build());
logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build());
LogRecord.Started startedLogRecord = logTtl.get();
if (null == startedLogRecord) {
return;
@@ -96,7 +87,7 @@ public class LogInterceptor implements HandlerInterceptor {
HandlerMethod handlerMethod = (HandlerMethod)handler;
Method targetMethod = handlerMethod.getMethod();
Class<?> targetClass = handlerMethod.getBeanType();
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, logProperties
.getIncludes(), targetMethod, targetClass);
logDao.add(logRecord);
} catch (Exception ex) {
@@ -118,10 +109,6 @@ public class LogInterceptor implements HandlerInterceptor {
if (!(handler instanceof HandlerMethod handlerMethod)) {
return false;
}
// 如果接口匹配排除列表,不记录日志
if (logProperties.isMatch(request.getRequestURI())) {
return false;
}
// 如果接口被隐藏,不记录日志
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
if (null != methodOperation && methodOperation.hidden()) {

View File

@@ -36,7 +36,7 @@ public class WebSocketProperties {
private static final List<String> ALL = Collections.singletonList(StringConstants.ASTERISK);
/**
* 是否启用 WebSocket
* 是否启用
*/
private boolean enabled = true;

View File

@@ -19,7 +19,6 @@ package top.continew.starter.ratelimiter.aop;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@@ -36,7 +35,7 @@ import top.continew.starter.ratelimiter.autoconfigure.RateLimiterProperties;
import top.continew.starter.ratelimiter.generator.RateLimiterNameGenerator;
import top.continew.starter.ratelimiter.enums.LimitType;
import top.continew.starter.ratelimiter.exception.RateLimiterException;
import top.continew.starter.web.util.SpringWebUtils;
import top.continew.starter.web.util.ServletUtils;
import java.lang.reflect.Method;
import java.time.Duration;
@@ -170,7 +169,7 @@ public class RateLimiterAspect {
}
// 获取后缀
String suffix = switch (rateLimiter.type()) {
case IP -> JakartaServletUtil.getClientIP(SpringWebUtils.getRequest());
case IP -> ServletUtils.getRequestIp();
case CLUSTER -> redissonClient.getId();
default -> StringConstants.EMPTY;
};

View File

@@ -28,11 +28,24 @@ import top.continew.starter.core.constant.PropertiesConstants;
@ConfigurationProperties(PropertiesConstants.RATE_LIMITER)
public class RateLimiterProperties {
/**
* 是否启用
*/
private boolean enabled = true;
/**
* Key 前缀
*/
private String keyPrefix = "RateLimiter";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getKeyPrefix() {
return keyPrefix;
}

View File

@@ -29,7 +29,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
public class CryptoProperties {
/**
* 是否启用加/解密配置
* 是否启用
*/
private boolean enabled = true;

View File

@@ -29,7 +29,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
public class PasswordEncoderProperties {
/**
* 是否启用密码编解码配置
* 是否启用
*/
private boolean enabled = true;

View File

@@ -33,7 +33,7 @@ import java.util.List;
public class XssProperties {
/**
* 是否启用 XSS 过滤
* 是否启用
*/
private boolean enabled = true;

View File

@@ -30,7 +30,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
public class TraceProperties {
/**
* 是否启用链路追踪配置
* 是否启用
*/
private boolean enabled = false;

View File

@@ -32,12 +32,6 @@
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- Hibernate Validator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Graceful Response一个Spring Boot技术栈下的优雅响应处理组件可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程提高开发效率提高代码质量 -->
<dependency>
<groupId>com.feiniaojin</groupId>

View File

@@ -36,7 +36,7 @@ public class CorsProperties {
private static final List<String> ALL = Collections.singletonList(StringConstants.ASTERISK);
/**
* 是否启用跨域配置
* 是否启用
*/
private boolean enabled = false;

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.server;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* 服务器配置属性
*
* @author Charles7c
* @since 2.11.0
*/
@ConfigurationProperties("server.extension")
public class ServerExtensionProperties {
/**
* 默认禁止三个不安全的 HTTP 方法(如 CONNECT、TRACE、TRACK
*/
private static final List<String> DEFAULT_ALLOWED_METHODS = List.of("CONNECT", "TRACE", "TRACK");
/**
* 是否启用
*/
private boolean enabled = true;
/**
* 不允许的请求方式
*/
private List<String> disallowedMethods = new ArrayList<>(DEFAULT_ALLOWED_METHODS);
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public List<String> getDisallowedMethods() {
return disallowedMethods;
}
public void setDisallowedMethods(List<String> disallowedMethods) {
this.disallowedMethods = disallowedMethods;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import io.undertow.Undertow;
import io.undertow.server.handlers.DisallowedMethodsHandler;
import io.undertow.util.HttpString;
import top.continew.starter.core.constant.PropertiesConstants;
import java.util.stream.Collectors;
/**
* Undertow 自动配置
*
* @author Jasmine
* @author Charles7c
* @since 2.11.0
*/
@AutoConfiguration
@ConditionalOnWebApplication
@ConditionalOnClass(Undertow.class)
@EnableConfigurationProperties(ServerExtensionProperties.class)
@ConditionalOnProperty(prefix = "server.extension", name = PropertiesConstants.ENABLED, havingValue = "true")
public class UndertowAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(UndertowAutoConfiguration.class);
/**
* Undertow 自定义配置
*/
@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> customize(ServerExtensionProperties properties) {
return factory -> {
factory.addDeploymentInfoCustomizers(deploymentInfo -> deploymentInfo
.addInitialHandlerChainWrapper(handler -> new DisallowedMethodsHandler(handler, properties
.getDisallowedMethods()
.stream()
.map(HttpString::tryFromString)
.collect(Collectors.toSet()))));
log.debug("[ContiNew Starter] - Disallowed HTTP methods on Server Undertow: {}.", properties
.getDisallowedMethods());
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Server Undertow' completed initialization.");
};
}
}

View File

@@ -17,22 +17,33 @@
package top.continew.starter.web.util;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.UriUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.json.jackson.util.JSONUtils;
import java.util.Collection;
import java.util.Map;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Servlet 工具类
*
* @author Charles7c
* @author echo
* @since 1.0.0
*/
public class ServletUtils {
public class ServletUtils extends JakartaServletUtil {
private ServletUtils() {
}
@@ -85,13 +96,138 @@ public class ServletUtils {
return userAgent.getOs().getName();
}
/**
* 获取请求方法
*
* @return {@link String }
* @since 2.11.0
*/
public static String getRequestMethod() {
HttpServletRequest request = getRequest();
return request != null ? request.getMethod() : null;
}
/**
* 获取请求参数
*
* @param name 参数名
* @return {@link String }
* @since 2.11.0
*/
public static String getRequestParameter(String name) {
HttpServletRequest request = getRequest();
return request != null ? request.getParameter(name) : null;
}
/**
* 获取请求 Ip
*
* @return {@link String }
* @since 2.11.0
*/
public static String getRequestIp() {
HttpServletRequest request = getRequest();
return request != null ? getClientIP(request) : null;
}
/**
* 获取请求头信息
*
* @return {@link Map }<{@link String }, {@link String }>
* @since 2.11.0
*/
public static Map<String, String> getRequestHeaders() {
HttpServletRequest request = getRequest();
return request != null ? getHeaderMap(request) : Collections.emptyMap();
}
/**
* 获取请求 URL包含 query 参数)
* <p>{@code http://localhost:8000/system/user?page=1&size=10}</p>
*
* @return {@link URI }
* @since 2.11.0
*/
public static URI getRequestUrl() {
HttpServletRequest request = getRequest();
if (request == null) {
return null;
}
String queryString = request.getQueryString();
if (CharSequenceUtil.isBlank(queryString)) {
return URI.create(request.getRequestURL().toString());
}
try {
StringBuilder urlBuilder = appendQueryString(queryString);
return new URI(urlBuilder.toString());
} catch (URISyntaxException e) {
String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8);
StringBuilder urlBuilder = appendQueryString(encoded);
return URI.create(urlBuilder.toString());
}
}
/**
* 获取请求路径
*
* @return {@link URI }
* @since 2.11.0
*/
public static String getRequestPath() {
HttpServletRequest request = getRequest();
return request != null ? request.getRequestURI() : null;
}
/**
* 获取请求 body 参数
*
* @return {@link String }
* @since 2.11.0
*/
public static String getRequestBody() {
HttpServletRequest request = getRequest();
if (request instanceof RepeatReadRequestWrapper wrapper && !wrapper.isMultipartContent(request)) {
String body = JakartaServletUtil.getBody(request);
return JSONUtils.isTypeJSON(body) ? body : null;
}
return null;
}
/**
* 获取请求参数
*
* @return {@link Map }<{@link String }, {@link Object }>
* @since 2.11.0
*/
public static Map<String, Object> getRequestParams() {
String body = getRequestBody();
return CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body)
? JSONUtils.toBean(body, Map.class)
: Collections.unmodifiableMap(JakartaServletUtil.getParamMap(Objects.requireNonNull(getRequest())));
}
/**
* 获取响应状态
*
* @return int
* @since 2.11.0
*/
public static int getResponseStatus() {
HttpServletResponse response = getResponse();
return response != null ? response.getStatus() : -1;
}
/**
* 获取响应所有的头header信息
*
* @param response 响应对象{@link HttpServletResponse}
* @return header值
* @since 2.11.0
*/
public static Map<String, String> getHeaderMap(HttpServletResponse response) {
public static Map<String, String> getResponseHeaders() {
HttpServletResponse response = getResponse();
if (response == null) {
return Collections.emptyMap();
}
final Collection<String> headerNames = response.getHeaderNames();
final Map<String, String> headerMap = MapUtil.newHashMap(headerNames.size(), true);
for (String name : headerNames) {
@@ -99,4 +235,102 @@ public class ServletUtils {
}
return headerMap;
}
/**
* 获取响应 body 参数
*
* @return {@link String }
* @since 2.11.0
*/
public static String getResponseBody() {
HttpServletResponse response = getResponse();
if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) {
String body = wrapper.getResponseContent();
return JSONUtils.isTypeJSON(body) ? body : null;
}
return null;
}
/**
* 获取响应参数
*
* @return {@link Map }<{@link String }, {@link Object }>
* @since 2.11.0
*/
public static Map<String, Object> getResponseParams() {
String body = getResponseBody();
return CharSequenceUtil.isNotBlank(body) && JSONUtils.isTypeJSON(body)
? JSONUtils.toBean(body, Map.class)
: null;
}
/**
* 获取 HTTP Session
*
* @return HttpSession
* @since 2.11.0
*/
public static HttpSession getSession() {
HttpServletRequest request = getRequest();
return request != null ? request.getSession() : null;
}
/**
* 获取 HTTP Request
*
* @return HttpServletRequest
* @since 2.11.0
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
return null;
}
return attributes.getRequest();
}
/**
* 获取 HTTP Response
*
* @return HttpServletResponse
* @since 2.11.0
*/
public static HttpServletResponse getResponse() {
ServletRequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
return null;
}
return attributes.getResponse();
}
/**
* 获取请求属性
*
* @return {@link ServletRequestAttributes }
* @since 2.11.0
*/
public static ServletRequestAttributes getRequestAttributes() {
try {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes)attributes;
} catch (Exception e) {
return null;
}
}
/**
* 追加查询字符串
*
* @param queryString 查询字符串
* @return {@link StringBuilder }
*/
private static StringBuilder appendQueryString(String queryString) {
HttpServletRequest request = getRequest();
if (request == null) {
return new StringBuilder();
}
return new StringBuilder().append(request.getRequestURL())
.append(StringConstants.QUESTION_MARK)
.append(queryString);
}
}

View File

@@ -20,13 +20,9 @@ import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.http.server.PathContainer;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
@@ -38,7 +34,6 @@ import top.continew.starter.core.constant.StringConstants;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Spring Web 工具类
@@ -51,24 +46,6 @@ public class SpringWebUtils {
private SpringWebUtils() {
}
/**
* 获取请求对象
*
* @return 请求对象
*/
public static HttpServletRequest getRequest() {
return getServletRequestAttributes().getRequest();
}
/**
* 获取响应对象
*
* @return 响应对象
*/
public static HttpServletResponse getResponse() {
return getServletRequestAttributes().getResponse();
}
/**
* 路径是否匹配
*
@@ -157,8 +134,4 @@ public class SpringWebUtils {
.getUrlMap();
ReflectUtil.<Void>invoke(resourceHandlerMapping, "registerHandlers", additionalUrlMap);
}
private static ServletRequestAttributes getServletRequestAttributes() {
return (ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes());
}
}

View File

@@ -1,2 +1,3 @@
top.continew.starter.web.autoconfigure.mvc.WebMvcAutoConfiguration
top.continew.starter.web.autoconfigure.cors.CorsAutoConfiguration
top.continew.starter.web.autoconfigure.cors.CorsAutoConfiguration
top.continew.starter.web.autoconfigure.server.UndertowAutoConfiguration

View File

@@ -22,4 +22,21 @@ continew-starter.web.response:
exclude-packages:
- io.swagger.**
- org.springdoc.**
- org.springframework.boot.actuate.*
- org.springframework.boot.actuate.*
--- ### 服务器配置
server:
## Undertow 服务器配置
undertow:
# HTTP POST 请求内容的大小上限(默认 -1不限制
max-http-post-size: -1
# 以下的配置会影响 buffer这些 buffer 会用于服务器连接的 IO 操作,有点类似 Netty 的池化内存管理
# 每块 buffer的空间大小越小的空间被利用越充分不要设置太大以免影响其他应用合适即可
buffer-size: 512
# 是否分配的直接内存NIO 直接分配的堆外内存)
direct-buffers: true
threads:
# 设置 IO 线程数,它主要执行非阻塞的任务,它们会负责多个连接(默认每个 CPU 核心一个线程)
io: 8
# 阻塞任务线程池,当执行类似 Servlet 请求阻塞操作Undertow 会从这个线程池中取得线程(它的值设置取决于系统的负载)
worker: 256

52
pom.xml
View File

@@ -19,31 +19,6 @@
ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken
可轻松集成到应用中,为开发人员减少手动引入依赖及配置的麻烦,为 Spring Boot Web 项目的灵活快速构建提供支持。
</description>
<url>https://github.com/continew-org/continew-starter</url>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<developers>
<developer>
<id>charles7c</id>
<name>Charles7c</name>
<email>charles7c@126.com</email>
<roles>
<role>Creator</role>
<role>Java Development Engineer</role>
</roles>
<timezone>+8</timezone>
<url>https://github.com/Charles7c</url>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:continew-org/continew-starter.git</connection>
<developerConnection>scm:git:git@github.com:continew-org/continew-starter.git</developerConnection>
<url>https://github.com/continew-org/continew-starter</url>
</scm>
<properties>
<!-- Maven Environment Versions -->
@@ -58,6 +33,7 @@
<modules>
<module>continew-starter-dependencies</module>
<module>continew-starter-bom</module>
<module>continew-starter-core</module>
<module>continew-starter-json</module>
<module>continew-starter-api-doc</module>
@@ -142,4 +118,30 @@
</plugin>
</plugins>
</build>
<url>https://github.com/continew-org/continew-starter</url>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<developers>
<developer>
<id>charles7c</id>
<name>Charles7c</name>
<email>charles7c@126.com</email>
<roles>
<role>Creator</role>
<role>Java Development Engineer</role>
</roles>
<timezone>+8</timezone>
<url>https://github.com/Charles7c</url>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:continew-org/continew-starter.git</connection>
<developerConnection>scm:git:git@github.com:continew-org/continew-starter.git</developerConnection>
<url>https://github.com/continew-org/continew-starter</url>
</scm>
</project>