mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-12 18:57:14 +08:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 13c4253b29 | |||
| d771e12839 | |||
| 6e621bc459 | |||
| 326dd76c34 | |||
| c72259028f | |||
| 5f6822742f | |||
| aa463dff37 | |||
| 4fe067a889 | |||
| 0b342d5c73 | |||
| 31e1d52ea4 | |||
| 279d72b724 | |||
| 80c0700934 | |||
|
|
b9779e8944 | ||
| a6c9d33024 | |||
| eb2cac54f7 | |||
| 13f606f631 | |||
| 0ea8f77d8a | |||
| 1b27107d22 | |||
| c3df4d7ef6 | |||
| 7ff26c4596 | |||
| 25f499de7e | |||
|
|
f7ed2bbfb0 | ||
| 88d11027dd | |||
| c5cb203532 | |||
| 5ff9391485 | |||
| 613599f921 | |||
| 0d334523e9 | |||
| 265c669eda | |||
|
|
c089df66cf | ||
|
|
7c3f15a6f6 | ||
| 75874171db | |||
| dc407a82cc |
32
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
32
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -6,6 +6,26 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
感谢您使用 ContiNew Starter!请您花些时间填写这份 Bug 报告。
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: 请您确认
|
||||
description: 在提交 Issue 之前,请确保执行过以下操作。
|
||||
options:
|
||||
- label: 尝试[最新版本](https://central.sonatype.com/artifact/top.continew/continew-starter/versions),仍有相同问题
|
||||
required: true
|
||||
- label: 阅读[使用指南](https://continew.top/starter/intro/what-is.html)
|
||||
required: true
|
||||
- label: 查找[常见问题](https://continew.top/faq.html)
|
||||
required: true
|
||||
- label: 根据报错信息(自行翻译英文)百度或 Google 一下
|
||||
required: true
|
||||
- label: 阅读源码并在 IDE 中进行断点调试
|
||||
required: false
|
||||
- label: 搜索是否有其他人提交过类似的 Issue,如果对应 Issue 尚未解决,您可以先订阅关注该 Issue(为了方便后来者查找问题解决方法,请尽量避免创建重复的 Issue)
|
||||
required: true
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
@@ -43,15 +63,3 @@ body:
|
||||
attributes:
|
||||
label: 额外补充
|
||||
description: 添加您的完整报错信息或屏幕截图,以及一切能帮助定位问题的信息。
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: 确认
|
||||
description: 在提交 issue 之前,请确保执行过以下操作。
|
||||
options:
|
||||
- label: 阅读文档
|
||||
required: true
|
||||
- label: 根据报错信息百度或 Google 一下
|
||||
required: true
|
||||
- label: 搜索是否有其他人提交过类似的 issue,如果对应 issue 尚未解决,您可以先订阅关注该 issue(为了方便后来者查找问题解决方法,请尽量避免创建重复的 issue)
|
||||
required: true
|
||||
28
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
28
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -5,6 +5,24 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
感谢您使用 ContiNew Starter!请您花些时间填写这份 Feature 调查。
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: 请您确认
|
||||
description: 在提交 Feature 之前,请确保执行过以下操作。
|
||||
options:
|
||||
- label: 阅读[使用指南](https://continew.top/starter/intro/what-is.html)
|
||||
required: true
|
||||
- label: 查找[常见问题](https://continew.top/faq.html)
|
||||
required: true
|
||||
- label: 查看[需求墙](https://continew.top/require.html)计划
|
||||
required: true
|
||||
- label: 搜索是否有其他人提交过类似的 Feature,如果对应 Feature 尚未完成,您可以先订阅关注该 Feature(为了方便后来者查找问题解决方法,请尽量避免创建重复的 Feature)
|
||||
required: true
|
||||
- label: 您是否愿意为您提出的 Feature 提交 PR?
|
||||
required: false
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
@@ -30,13 +48,3 @@ body:
|
||||
attributes:
|
||||
label: 额外补充
|
||||
description: 添加您在其他框架或场景遇见的效果截图或链接,以及一切能帮助理解 Feature 的信息。
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: 确认
|
||||
description: 在提交 issue 之前,请确保执行过以下操作。
|
||||
options:
|
||||
- label: 阅读文档
|
||||
required: true
|
||||
- label: 搜索是否有其他人提交过类似的 issue,如果对应 issue 尚未解决,您可以先订阅关注该 issue(为了方便后来者查找问题解决方法,请尽量避免创建重复的 issue)
|
||||
required: true
|
||||
BIN
.image/qrcode.jpg
Normal file
BIN
.image/qrcode.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
76
CHANGELOG.md
76
CHANGELOG.md
@@ -1,3 +1,79 @@
|
||||
## [v2.8.3](https://github.com/continew-org/continew-starter/compare/v2.8.2...v2.8.3) (2025-01-16)
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【log】调整所属模块、日志描述默认提示,不再直接抛出异常 ([326dd76](https://github.com/continew-org/continew-starter/commit/326dd76c34476141c39add5348da052bdb8c27cd))
|
||||
- 【extension/crud】移除 BaseDO、BaseCreateDO、BaseUpdateDO 等(已移动到 Admin 项目内) ([6e621bc](https://github.com/continew-org/continew-starter/commit/6e621bc4597996c8f1f65c542f5faa922b95a900))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【extension/crud】修复查询条件校验无效的问题 ([d771e12](https://github.com/continew-org/continew-starter/commit/d771e128399851fa78f1041fa4ffcd6af3332fcd))
|
||||
|
||||
## [v2.8.2](https://github.com/continew-org/continew-starter/compare/v2.8.1...v2.8.2) (2025-01-09)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【core】SpringUtils 新增获取代理对象方法 ([5f68227](https://github.com/continew-org/continew-starter/commit/5f6822742fd0f032bcc351155f0b966d24b05346))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【extension/crud】移除 CommonUserService、ContainerPool(已移动到 Admin 项目内) ([0b342d5](https://github.com/continew-org/continew-starter/commit/0b342d5c73e95b809337b939b4e1e957374bad85))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【log】修复日志记录时所属模块和描述取值优先级失效的问题 ([4fe067a](https://github.com/continew-org/continew-starter/commit/4fe067a889f00617f03caf7ae3598466560dce33))
|
||||
|
||||
### 📦 依赖升级
|
||||
|
||||
- graceful-response 5.0.4-boot3 => 5.0.5-boot3(修复父类参数校验异常) ([aa463df](https://github.com/continew-org/continew-starter/commit/aa463dff37b658d1cb2a69e68f54790e03c4103d))
|
||||
|
||||
## [v2.8.1](https://github.com/continew-org/continew-starter/compare/v2.8.0...v2.8.1) (2025-01-06)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【core】BaseEnum 新增 getByValue、getByDescription、isValidValue 方法 ([279d72b](https://github.com/continew-org/continew-starter/commit/279d72b7242bf996f9b88d38ed0ea7aa0a0d1c46))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【extension/crud】移除 BaseResp、BaseDetailResp(已移动到 Admin 项目内) ([eb2cac5](https://github.com/continew-org/continew-starter/commit/eb2cac54f75b2850f2957b32190d12e63377c185))
|
||||
- 【log】优化日志处理器解析 description、module 方法 ([a6c9d33](https://github.com/continew-org/continew-starter/commit/a6c9d33024ea70bb3dbe11981cbc9a3f9027bcd2))
|
||||
- 解决 Sonar 问题,替换部分过期 API ([80c0700](https://github.com/continew-org/continew-starter/commit/80c070093498abb8dff5529d177e1e2519577bf0))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【file/excel】优化 BaseEnum 转换器 (GitHub#10@Solution-Lin) ([b9779e8](https://github.com/continew-org/continew-starter/commit/b9779e894464ec534bebdd230a7239b6d1964ddb))
|
||||
|
||||
## [v2.8.0](https://github.com/continew-org/continew-starter/compare/v2.7.5...v2.8.0) (2024-12-25)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【log/aop】新增 log-aop 组件模块(基于 AOP 实现日志记录) (Gitee#36@dom-w) ([7c3f15a](https://github.com/continew-org/continew-starter/commit/7c3f15a6f647afabb061e560ad3335d47806d33f)) ([265c669](https://github.com/continew-org/continew-starter/commit/265c669eda7599172cc189c96428629423158e86))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【log】新增 LogHandler 提升日志模块的复用性 ([0d33452](https://github.com/continew-org/continew-starter/commit/0d334523e9d18b548740af6583521b47b8171446)) ([c5cb203](https://github.com/continew-org/continew-starter/commit/c5cb203532ea89b497121246f11ad858f1c3ac79)) ([7ff26c4](https://github.com/continew-org/continew-starter/commit/7ff26c45962e916370aeaeaa547974dbf727fdb4))
|
||||
- 【extension/tenant】多租户组件适配动态隔离级别 (GitHub#8@xtanyu) ([c089df6](https://github.com/continew-org/continew-starter/commit/c089df66cf226aa8062dc7ac2c82fb0111cfc5c0)) ([613599f](https://github.com/continew-org/continew-starter/commit/613599f92199e0cde11896d41c2d090bfdc46dd3)) ([88d1102](https://github.com/continew-org/continew-starter/commit/88d11027dd18eab5a0a71f85b135a1ddc0f3942f)) ([f7ed2bb](https://github.com/continew-org/continew-starter/commit/f7ed2bbfb017667253ec50341a753b89d65562bb)) ([25f499d](https://github.com/continew-org/continew-starter/commit/25f499de7eb59b53548d9d3f6826358b2fd0c40b))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【extension/crud】修复 PageResp 手动分页计算错误 ([dc407a8](https://github.com/continew-org/continew-starter/commit/dc407a82cca016db6896104804eef9b660d9d5a1))
|
||||
|
||||
### 📦 依赖升级
|
||||
|
||||
- Spring Boot 3.2.7 => 3.2.10 ([5ff9391](https://github.com/continew-org/continew-starter/commit/5ff93914854098ac05bf24559336e2155b8f1503))
|
||||
- Spring Boot 3.2.10 => 3.2.12
|
||||
- SnailJob 1.1.2 => 1.2.0
|
||||
- JustAuth 1.16.6 => 1.16.7
|
||||
- MyBatis Flex 1.9.7 => 1.10.3
|
||||
- JetCache 2.7.6 => 2.7.7
|
||||
- Redisson 3.36.0 => 3.41.0
|
||||
- CosID 2.9.9 => 2.10.1
|
||||
- nashorn-core 15.4 => 15.5
|
||||
- aws-s3 1.12.771 => 1.12.780
|
||||
- graceful-response 5.0.0-boot3 => 5.0.4-boot3
|
||||
- ip2region 3.2.6 => 3.2.12
|
||||
- Hutool 5.8.32 => 5.8.34
|
||||
|
||||
## [v2.7.5](https://github.com/continew-org/continew-starter/compare/v2.7.4...v2.7.5) (2024-12-06)
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
64
README.md
64
README.md
@@ -1,34 +1,38 @@
|
||||
# ContiNew Starter
|
||||
|
||||
<a href="https://github.com/continew-org/continew-starter/blob/dev/LICENSE" target="_blank">
|
||||
<img src="https://img.shields.io/badge/License-LGPL--3.0-blue.svg" alt="License" />
|
||||
</a>
|
||||
<a href="https://central.sonatype.com/search?q=continew-starter" target="_blank">
|
||||
<a href="https://central.sonatype.com/search?q=continew-starter" title="Release" target="_blank">
|
||||
<img src="https://img.shields.io/maven-central/v/top.continew/continew-starter.svg?label=Maven%20Central&logo=sonatype&logoColor=FFF" alt="Release" />
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/continew-org/continew-starter/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade" target="_blank">
|
||||
<img src="https://app.codacy.com/project/badge/Grade/90ed633957a9410aa8745f0654827c01" alt="Codacy Badge" />
|
||||
<a href="https://spring.io/projects/spring-boot" title="Spring Boot" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Spring Boot-3.2.12-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
|
||||
</a>
|
||||
<a href="https://sonarcloud.io/summary/new_code?id=Charles7c_continew-starter" target="_blank">
|
||||
<img src="https://sonarcloud.io/api/project_badges/measure?project=Charles7c_continew-starter&metric=alert_status" alt="Sonar Status" />
|
||||
</a>
|
||||
<a href="https://spring.io/projects/spring-boot" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Spring Boot-3.2.7-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
|
||||
</a>
|
||||
<a href="https://github.com/continew-org/continew-starter" target="_blank">
|
||||
<a href="https://github.com/continew-org/continew-starter" title="Open JDK" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Open JDK-17-%236CB52D.svg?logo=OpenJDK&logoColor=FFF" alt="Open JDK" />
|
||||
</a>
|
||||
<a href="https://github.com/continew-org/continew-starter" target="_blank">
|
||||
<img src="https://img.shields.io/github/stars/continew-org/continew-starter?style=social" alt="GitHub stars" />
|
||||
<a href="https://app.codacy.com/gh/continew-org/continew-starter/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade" title="Codacy" target="_blank">
|
||||
<img src="https://app.codacy.com/project/badge/Grade/90ed633957a9410aa8745f0654827c01" alt="Codacy" />
|
||||
</a>
|
||||
<a href="https://github.com/continew-org/continew-starter" target="_blank">
|
||||
<img src="https://img.shields.io/github/forks/continew-org/continew-starter?style=social" alt="GitHub forks" />
|
||||
<a href="https://sonarcloud.io/summary/new_code?id=Charles7c_continew-starter" title="Sonar" target="_blank">
|
||||
<img src="https://sonarcloud.io/api/project_badges/measure?project=Charles7c_continew-starter&metric=alert_status" alt="Sonar" />
|
||||
</a>
|
||||
<a href="https://gitee.com/continew/continew-starter" target="_blank">
|
||||
<img src="https://gitee.com/continew/continew-starter/badge/star.svg?theme=white" alt="Gitee stars" />
|
||||
<br />
|
||||
<a href="https://github.com/continew-org/continew-starter/blob/dev/LICENSE" title="License" target="_blank">
|
||||
<img src="https://img.shields.io/badge/License-LGPL--3.0-blue.svg" alt="License" />
|
||||
</a>
|
||||
<a href="https://gitee.com/continew/continew-starter" target="_blank">
|
||||
<img src="https://gitee.com/continew/continew-starter/badge/fork.svg?theme=white" alt="Gitee forks" />
|
||||
<a href="https://github.com/continew-org/continew-starter" title="GitHub Stars" target="_blank">
|
||||
<img src="https://img.shields.io/github/stars/continew-org/continew-starter?style=social" alt="GitHub Stars" />
|
||||
</a>
|
||||
<a href="https://github.com/continew-org/continew-starter" title="GitHub Forks" target="_blank">
|
||||
<img src="https://img.shields.io/github/forks/continew-org/continew-starter?style=social" alt="GitHub Forks" />
|
||||
</a>
|
||||
<a href="https://gitee.com/continew/continew-starter" title="Gitee Stars" target="_blank">
|
||||
<img src="https://gitee.com/continew/continew-starter/badge/star.svg?theme=dark" alt="Gitee Stars" />
|
||||
</a>
|
||||
<a href="https://gitee.com/continew/continew-starter" title="Gitee Forks" target="_blank">
|
||||
<img src="https://gitee.com/continew/continew-starter/badge/fork.svg?theme=dark" alt="Gitee Forks" />
|
||||
</a>
|
||||
<a href="https://gitcode.com/continew/continew-starter" title="GitCode Stars" target="_blank">
|
||||
<img src="https://gitcode.com/continew/continew-starter/star/badge.svg" alt="GitCode Stars" />
|
||||
</a>
|
||||
|
||||
## 简介
|
||||
@@ -173,7 +177,8 @@ continew-starter
|
||||
│ └─ continew-starter-messaging-websocket(WebSocket)
|
||||
├─ continew-starter-log(日志模块)
|
||||
│ ├─ continew-starter-log-core(通用模块)
|
||||
│ └─ continew-starter-log-interceptor(拦截器版(Spring Boot Actuator HttpTrace 增强版))
|
||||
│ ├─ continew-starter-log-aop(基于 AOP 实现)
|
||||
│ └─ continew-starter-log-interceptor(基于拦截器实现(Spring Boot Actuator HttpTrace 增强版))
|
||||
├─ continew-starter-file(文件处理模块)
|
||||
│ └─ continew-starter-file-excel(Easy Excel)
|
||||
├─ continew-starter-storage(存储模块)
|
||||
@@ -224,22 +229,11 @@ ContiNew Starter 的分支目前分为下个大版本的开发分支和上个大
|
||||
|
||||
## 反馈交流
|
||||
|
||||
💬 欢迎各位小伙伴儿扫描下方二维码加好友,备注 `cnadmin`,拉你进群,探讨技术、提提需求~
|
||||
|
||||
加入交流群后,你将会:
|
||||
|
||||
- 第一时间收到框架动态
|
||||
- 第一时间收到框架更新通知
|
||||
- 第一时间收到框架 Bug 通知
|
||||
- 和众多大佬互相 (huá shuǐ) 交流 (mō yú)
|
||||
欢迎各位小伙伴儿扫描下方二维码加入项目交流群,与项目维护团队及其他大佬用户实时交流讨论。
|
||||
|
||||
<div align="left">
|
||||
<img src="https://continew.top/qrcode.jpg" alt="二维码" width="230px" />
|
||||
<img src=".image/qrcode.jpg" alt="二维码" height="230px" />
|
||||
</div>
|
||||
<details>
|
||||
<summary>无加群意愿</summary>
|
||||
💬 如无加群意愿,欢迎在 <a href="https://github.com/continew-org/continew-starter/issues" target="_blank">Issues</a> 中反馈交流~ 🍻
|
||||
</details>
|
||||
|
||||
## 鸣谢
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.springdoc.core.service.SecurityService;
|
||||
import org.springdoc.core.utils.PropertyResolverUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import java.io.StringReader;
|
||||
@@ -152,7 +151,7 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
if (this.openAPI.getPaths() == null) {
|
||||
this.openAPI.setPaths(new Paths());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(this.openAPI.getServers())) {
|
||||
if (CollUtil.isNotEmpty(this.openAPI.getServers())) {
|
||||
this.isServersPresent = true;
|
||||
}
|
||||
}
|
||||
@@ -176,7 +175,7 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
|
||||
buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
|
||||
|
||||
if (!CollectionUtils.isEmpty(tagsStr)) {
|
||||
if (CollUtil.isNotEmpty(tagsStr)) {
|
||||
tagsStr = tagsStr.stream()
|
||||
.map(str -> propertyResolverUtils.resolve(str, locale))
|
||||
.collect(Collectors.toSet());
|
||||
@@ -190,8 +189,8 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
}
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(tagsStr)) {
|
||||
if (CollectionUtils.isEmpty(operation.getTags())) {
|
||||
if (CollUtil.isNotEmpty(tagsStr)) {
|
||||
if (CollUtil.isEmpty(operation.getTags())) {
|
||||
operation.setTags(new ArrayList<>(tagsStr));
|
||||
} else {
|
||||
Set<String> operationTagsSet = new HashSet<>(operation.getTags());
|
||||
@@ -225,10 +224,10 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
}
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(tags)) {
|
||||
if (CollUtil.isNotEmpty(tags)) {
|
||||
// Existing tags
|
||||
List<Tag> openApiTags = openAPI.getTags();
|
||||
if (!CollectionUtils.isEmpty(openApiTags)) {
|
||||
if (CollUtil.isNotEmpty(openApiTags)) {
|
||||
tags.addAll(openApiTags);
|
||||
}
|
||||
openAPI.setTags(new ArrayList<>(tags));
|
||||
@@ -256,7 +255,7 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
.collect(Collectors.toSet());
|
||||
methodTags.addAll(AnnotatedElementUtils
|
||||
.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
|
||||
if (!CollectionUtils.isEmpty(methodTags)) {
|
||||
if (CollUtil.isNotEmpty(methodTags)) {
|
||||
tagsStr.addAll(toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
|
||||
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
|
||||
addTags(allTags, tags, locale);
|
||||
|
||||
@@ -19,6 +19,7 @@ package top.continew.starter.cache.redisson.util;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.redisson.api.*;
|
||||
import org.redisson.api.options.KeysScanOptions;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -191,7 +192,9 @@ public class RedisUtils {
|
||||
* @return 缓存列表
|
||||
*/
|
||||
public static Collection<String> keys(String pattern) {
|
||||
return CLIENT.getKeys().getKeysStreamByPattern(pattern).toList();
|
||||
KeysScanOptions options = KeysScanOptions.defaults();
|
||||
options.pattern(pattern);
|
||||
return CLIENT.getKeys().getKeysStream(options).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,8 +368,21 @@ public class RedisUtils {
|
||||
* @return true:成功;false:失败
|
||||
*/
|
||||
public static boolean rateLimit(String key, RateType rateType, int rate, int rateInterval) {
|
||||
return rateLimit(key, rateType, rate, Duration.ofSeconds(rateInterval));
|
||||
}
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @param key 键
|
||||
* @param rateType 限流类型(OVERALL:全局限流;PER_CLIENT:单机限流)
|
||||
* @param rate 速率(指定时间间隔产生的令牌数)
|
||||
* @param rateInterval 速率间隔(时间间隔)
|
||||
* @return true:成功;false:失败
|
||||
*/
|
||||
public static boolean rateLimit(String key, RateType rateType, int rate, Duration rateInterval) {
|
||||
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
|
||||
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
|
||||
rateLimiter.trySetRate(rateType, rate, rateInterval);
|
||||
return rateLimiter.tryAcquire(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,6 @@ public class AsyncAutoConfiguration implements AsyncConfigurer {
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return (throwable, method, objects) -> {
|
||||
throwable.printStackTrace();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Exception message: ")
|
||||
.append(throwable.getMessage())
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package top.continew.starter.core.enums;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 枚举接口
|
||||
@@ -49,4 +50,50 @@ public interface BaseEnum<T extends Serializable> {
|
||||
default String getColor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据枚举值获取
|
||||
*
|
||||
* @param value 枚举值
|
||||
* @param clazz 枚举类
|
||||
* @return 枚举对象
|
||||
* @since 2.8.1
|
||||
*/
|
||||
static <E extends Enum<E> & BaseEnum, T> E getByValue(T value, Class<E> clazz) {
|
||||
for (E e : clazz.getEnumConstants()) {
|
||||
if (Objects.equals(e.getValue(), value)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据枚举描述获取
|
||||
*
|
||||
* @param description 枚举描述
|
||||
* @param clazz 枚举类
|
||||
* @return 枚举对象
|
||||
* @since 2.8.1
|
||||
*/
|
||||
static <E extends Enum<E> & BaseEnum> E getByDescription(String description, Class<?> clazz) {
|
||||
for (Object e : clazz.getEnumConstants()) {
|
||||
if (e instanceof BaseEnum<?> baseEnum && Objects.equals(baseEnum.getDescription(), description)) {
|
||||
return (E)baseEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断枚举值是否有效
|
||||
*
|
||||
* @param value 枚举值
|
||||
* @param clazz 枚举类
|
||||
* @return 是否有效
|
||||
* @since 2.8.1
|
||||
*/
|
||||
static <E extends Enum<E> & BaseEnum, T> boolean isValidValue(T value, Class<E> clazz) {
|
||||
return getByValue(value, clazz) != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.core.util;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
|
||||
/**
|
||||
* Spring 工具类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.8.2
|
||||
*/
|
||||
public class SpringUtils {
|
||||
|
||||
private SpringUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理对象
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param <T> 目标对象类型
|
||||
* @return 代理对象
|
||||
* @since 2.8.2
|
||||
*/
|
||||
public static <T> T getProxy(T target) {
|
||||
return (T)SpringUtil.getBean(target.getClass());
|
||||
}
|
||||
}
|
||||
@@ -183,12 +183,11 @@ public class QueryWrapperHelper {
|
||||
* @param queryType 查询类型
|
||||
* @param columnName 列名
|
||||
* @param fieldValue 字段值
|
||||
* @param <R> 查询数据类型
|
||||
*/
|
||||
private static <R> void parse(QueryType queryType,
|
||||
String columnName,
|
||||
Object fieldValue,
|
||||
List<Consumer<QueryWrapper>> consumers) {
|
||||
private static void parse(QueryType queryType,
|
||||
String columnName,
|
||||
Object fieldValue,
|
||||
List<Consumer<QueryWrapper>> consumers) {
|
||||
switch (queryType) {
|
||||
case EQ -> consumers.add(q -> q.eq(columnName, fieldValue));
|
||||
case NE -> consumers.add(q -> q.ne(columnName, fieldValue));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>3.2.10</version>
|
||||
<version>3.2.12</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
@@ -43,33 +43,33 @@
|
||||
|
||||
<properties>
|
||||
<!-- 项目版本号 -->
|
||||
<revision>2.7.5</revision>
|
||||
<snail-job.version>1.1.2</snail-job.version>
|
||||
<revision>2.8.3</revision>
|
||||
<snail-job.version>1.2.0</snail-job.version>
|
||||
<sa-token.version>1.39.0</sa-token.version>
|
||||
<just-auth.version>1.16.6</just-auth.version>
|
||||
<just-auth.version>1.16.7</just-auth.version>
|
||||
<mybatis-plus.version>3.5.8</mybatis-plus.version>
|
||||
<mybatis-flex.version>1.9.7</mybatis-flex.version>
|
||||
<mybatis-flex.version>1.10.3</mybatis-flex.version>
|
||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<jetcache.version>2.7.6</jetcache.version>
|
||||
<redisson.version>3.36.0</redisson.version>
|
||||
<cosid.version>2.9.9</cosid.version>
|
||||
<jetcache.version>2.7.7</jetcache.version>
|
||||
<redisson.version>3.41.0</redisson.version>
|
||||
<cosid.version>2.10.1</cosid.version>
|
||||
<sms4j.version>3.3.3</sms4j.version>
|
||||
<aj-captcha.version>1.3.0</aj-captcha.version>
|
||||
<easy-captcha.version>1.6.2</easy-captcha.version>
|
||||
<easy-excel.version>3.3.4</easy-excel.version>
|
||||
<nashorn.version>15.4</nashorn.version>
|
||||
<nashorn.version>15.5</nashorn.version>
|
||||
<x-file-storage.version>2.2.1</x-file-storage.version>
|
||||
<aws-s3.version>1.12.771</aws-s3.version>
|
||||
<graceful-response.version>5.0.0-boot3</graceful-response.version>
|
||||
<aws-s3.version>1.12.780</aws-s3.version>
|
||||
<graceful-response.version>5.0.5-boot3</graceful-response.version>
|
||||
<crane4j.version>2.9.0</crane4j.version>
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
<tlog.version>1.5.2</tlog.version>
|
||||
<snakeyaml.version>2.3</snakeyaml.version>
|
||||
<okhttp.version>4.12.0</okhttp.version>
|
||||
<ttl.version>2.14.5</ttl.version>
|
||||
<ip2region.version>3.2.6</ip2region.version>
|
||||
<hutool.version>5.8.32</hutool.version>
|
||||
<ip2region.version>3.2.12</ip2region.version>
|
||||
<hutool.version>5.8.34</hutool.version>
|
||||
<!-- Maven Plugin Versions -->
|
||||
<flatten.version>1.6.0</flatten.version>
|
||||
<spotless.version>2.43.0</spotless.version>
|
||||
@@ -483,13 +483,20 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - 拦截器版(Spring Boot Actuator HttpTrace 增强版) -->
|
||||
<!-- 日志模块 - 基于拦截器实现(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>
|
||||
@@ -680,7 +687,8 @@
|
||||
<configuration>
|
||||
<artifacts>
|
||||
<artifact>
|
||||
<file>${project.build.directory}/effective-pom/continew-starter-dependencies.xml</file>
|
||||
<file>${project.build.directory}/effective-pom/continew-starter-dependencies.xml
|
||||
</file>
|
||||
<type>effective-pom</type>
|
||||
</artifact>
|
||||
</artifacts>
|
||||
|
||||
@@ -30,7 +30,6 @@ 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.req.BaseReq;
|
||||
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
|
||||
import top.continew.starter.extension.crud.model.resp.BasePageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
@@ -49,7 +48,7 @@ import java.util.List;
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>, L, D, Q, C extends BaseReq> implements CrudApiHandler {
|
||||
public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>, L, D, Q, C> implements CrudApiHandler {
|
||||
|
||||
@Autowired
|
||||
protected S baseService;
|
||||
@@ -65,7 +64,7 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
|
||||
@Operation(summary = "分页查询列表", description = "分页查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping
|
||||
public BasePageResp<L> page(Q query, @Validated PageQuery pageQuery) {
|
||||
public BasePageResp<L> page(@Validated Q query, @Validated PageQuery pageQuery) {
|
||||
return baseService.page(query, pageQuery);
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
|
||||
@Operation(summary = "查询列表", description = "查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/list")
|
||||
public List<L> list(Q query, SortQuery sortQuery) {
|
||||
public List<L> list(@Validated Q query, @Validated SortQuery sortQuery) {
|
||||
return baseService.list(query, sortQuery);
|
||||
}
|
||||
|
||||
@@ -95,7 +94,7 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
public List<Tree<Long>> tree(@Validated Q query, @Validated SortQuery sortQuery) {
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
@@ -168,7 +167,7 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
|
||||
@ExcludeFromGracefulResponse
|
||||
@Operation(summary = "导出数据", description = "导出数据")
|
||||
@GetMapping("/export")
|
||||
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
|
||||
public void export(@Validated Q query, @Validated SortQuery sortQuery, HttpServletResponse response) {
|
||||
baseService.export(query, sortQuery, response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +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.extension.crud.model.resp;
|
||||
|
||||
import cn.crane4j.annotation.Assemble;
|
||||
import cn.crane4j.annotation.Mapping;
|
||||
import cn.crane4j.annotation.condition.ConditionOnPropertyNotNull;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import top.continew.starter.extension.crud.constant.ContainerPool;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 详情响应基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BaseDetailResp extends BaseResp {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
@JsonIgnore
|
||||
@ConditionOnPropertyNotNull
|
||||
@Assemble(container = ContainerPool.USER_NICKNAME, props = @Mapping(ref = "updateUserString"))
|
||||
private Long updateUser;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
@Schema(description = "修改人", example = "李四")
|
||||
@ExcelProperty(value = "修改人", order = Integer.MAX_VALUE - 2)
|
||||
private String updateUserString;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@Schema(description = "修改时间", example = "2023-08-08 08:08:08", type = "string")
|
||||
@ExcelProperty(value = "修改时间", order = Integer.MAX_VALUE - 1)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Long getUpdateUser() {
|
||||
return updateUser;
|
||||
}
|
||||
|
||||
public void setUpdateUser(Long updateUser) {
|
||||
this.updateUser = updateUser;
|
||||
}
|
||||
|
||||
public String getUpdateUserString() {
|
||||
return updateUserString;
|
||||
}
|
||||
|
||||
public void setUpdateUserString(String updateUserString) {
|
||||
this.updateUserString = updateUserString;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
@@ -1,116 +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.extension.crud.model.resp;
|
||||
|
||||
import cn.crane4j.annotation.Assemble;
|
||||
import cn.crane4j.annotation.Mapping;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import top.continew.starter.extension.crud.constant.ContainerPool;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 响应参数基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BaseResp implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@Schema(description = "ID", example = "1")
|
||||
@ExcelProperty(value = "ID", order = 1)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Assemble(container = ContainerPool.USER_NICKNAME, props = @Mapping(ref = "createUserString"))
|
||||
private Long createUser;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@Schema(description = "创建人", example = "超级管理员")
|
||||
@ExcelProperty(value = "创建人", order = Integer.MAX_VALUE - 4)
|
||||
private String createUserString;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string")
|
||||
@ExcelProperty(value = "创建时间", order = Integer.MAX_VALUE - 3)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 是否禁用修改
|
||||
*/
|
||||
@Schema(description = "是否禁用修改", example = "true")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Boolean disabled;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getCreateUser() {
|
||||
return createUser;
|
||||
}
|
||||
|
||||
public void setCreateUser(Long createUser) {
|
||||
this.createUser = createUser;
|
||||
}
|
||||
|
||||
public String getCreateUserString() {
|
||||
return createUserString;
|
||||
}
|
||||
|
||||
public void setCreateUserString(String createUserString) {
|
||||
this.createUserString = createUserString;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public Boolean getDisabled() {
|
||||
return disabled;
|
||||
}
|
||||
|
||||
public void setDisabled(Boolean disabled) {
|
||||
this.disabled = disabled;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +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.extension.crud.service;
|
||||
|
||||
import cn.crane4j.annotation.ContainerMethod;
|
||||
import cn.crane4j.annotation.MappingType;
|
||||
import top.continew.starter.extension.crud.constant.ContainerPool;
|
||||
|
||||
/**
|
||||
* 公共用户业务接口
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface CommonUserService {
|
||||
|
||||
/**
|
||||
* 根据 ID 查询昵称
|
||||
*
|
||||
* @param id ID
|
||||
* @return 昵称
|
||||
*/
|
||||
@ContainerMethod(namespace = ContainerPool.USER_NICKNAME, type = MappingType.ORDER_OF_KEYS)
|
||||
String getNicknameById(Long id);
|
||||
}
|
||||
@@ -1,62 +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.extension.crud.model.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* <p>
|
||||
* 通用字段:创建人、创建时间
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public class BaseCreateDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createUser;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
public Long getCreateUser() {
|
||||
return createUser;
|
||||
}
|
||||
|
||||
public void setCreateUser(Long createUser) {
|
||||
this.createUser = createUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
}
|
||||
@@ -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.extension.crud.model.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BaseDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createUser;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
private Long updateUser;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Long getCreateUser() {
|
||||
return createUser;
|
||||
}
|
||||
|
||||
public void setCreateUser(Long createUser) {
|
||||
this.createUser = createUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public Long getUpdateUser() {
|
||||
return updateUser;
|
||||
}
|
||||
|
||||
public void setUpdateUser(Long updateUser) {
|
||||
this.updateUser = updateUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +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.extension.crud.model.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* <p>
|
||||
* 通用字段:创建人、创建时间
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public class BaseUpdateDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
private Long updateUser;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Long getUpdateUser() {
|
||||
return updateUser;
|
||||
}
|
||||
|
||||
public void setUpdateUser(Long updateUser) {
|
||||
this.updateUser = updateUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
@@ -93,12 +93,10 @@ public class PageResp<L> extends BasePageResp<L> {
|
||||
pageResp.setTotal(list.size());
|
||||
// 对列表数据进行分页
|
||||
int fromIndex = (page - 1) * size;
|
||||
int toIndex = page * size + fromIndex;
|
||||
if (fromIndex > list.size()) {
|
||||
if (fromIndex >= list.size()) {
|
||||
pageResp.setList(new ArrayList<>(0));
|
||||
} else if (toIndex >= list.size()) {
|
||||
pageResp.setList(list.subList(fromIndex, list.size()));
|
||||
} else {
|
||||
int toIndex = Math.min(fromIndex + size, list.size());
|
||||
pageResp.setList(list.subList(fromIndex, toIndex));
|
||||
}
|
||||
return pageResp;
|
||||
|
||||
@@ -1,67 +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.extension.crud.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* <p>
|
||||
* 通用字段:创建人、创建时间
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public class BaseCreateDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long createUser;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
public Long getCreateUser() {
|
||||
return createUser;
|
||||
}
|
||||
|
||||
public void setCreateUser(Long createUser) {
|
||||
this.createUser = createUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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.extension.crud.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BaseDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long createUser;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Long updateUser;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Long getCreateUser() {
|
||||
return createUser;
|
||||
}
|
||||
|
||||
public void setCreateUser(Long createUser) {
|
||||
this.createUser = createUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public Long getUpdateUser() {
|
||||
return updateUser;
|
||||
}
|
||||
|
||||
public void setUpdateUser(Long updateUser) {
|
||||
this.updateUser = updateUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +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.extension.crud.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* <p>
|
||||
* 通用字段:创建人、创建时间
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public class BaseUpdateDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Long updateUser;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Long getUpdateUser() {
|
||||
return updateUser;
|
||||
}
|
||||
|
||||
public void setUpdateUser(Long updateUser) {
|
||||
this.updateUser = updateUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
@@ -93,12 +93,10 @@ public class PageResp<L> extends BasePageResp<L> {
|
||||
pageResp.setTotal(list.size());
|
||||
// 对列表数据进行分页
|
||||
int fromIndex = (page - 1) * size;
|
||||
int toIndex = page * size + fromIndex;
|
||||
if (fromIndex > list.size()) {
|
||||
if (fromIndex >= list.size()) {
|
||||
pageResp.setList(new ArrayList<>(0));
|
||||
} else if (toIndex >= list.size()) {
|
||||
pageResp.setList(list.subList(fromIndex, list.size()));
|
||||
} else {
|
||||
int toIndex = Math.min(fromIndex + size, list.size());
|
||||
pageResp.setList(list.subList(fromIndex, toIndex));
|
||||
}
|
||||
return pageResp;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.tenant.handler;
|
||||
package top.continew.starter.extension.tenant;
|
||||
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||
|
||||
@@ -31,9 +31,9 @@ public interface TenantDataSourceHandler {
|
||||
/**
|
||||
* 切换数据源
|
||||
*
|
||||
* @param dataSourceName 数据源名称
|
||||
* @param tenantDataSource 数据源配置
|
||||
*/
|
||||
void changeDataSource(String dataSourceName);
|
||||
void changeDataSource(TenantDataSource tenantDataSource);
|
||||
|
||||
/**
|
||||
* 是否存在指定数据源
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.tenant;
|
||||
|
||||
/**
|
||||
* 租户处理器
|
||||
*
|
||||
* @author 小熊
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public interface TenantHandler {
|
||||
|
||||
/**
|
||||
* 在指定租户中执行
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param runnable 方法
|
||||
*/
|
||||
void execute(Long tenantId, Runnable runnable);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ package top.continew.starter.extension.tenant.annotation;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 多租户数据源级隔离忽略注解
|
||||
* 多租户忽略注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
@@ -27,5 +27,5 @@ import java.lang.annotation.*;
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TenantDataSourceIgnore {
|
||||
public @interface TenantIgnore {
|
||||
}
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
package top.continew.starter.extension.tenant.autoconfigure;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import top.continew.starter.extension.tenant.context.TenantContext;
|
||||
import top.continew.starter.extension.tenant.annotation.TenantIgnore;
|
||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||
|
||||
/**
|
||||
@@ -29,20 +31,31 @@ import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class TenantInterceptor implements HandlerInterceptor {
|
||||
public class TenantInterceptor implements HandlerInterceptor, Ordered {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
private final TenantProvider tenantProvider;
|
||||
|
||||
public TenantInterceptor(TenantProperties tenantProperties) {
|
||||
public TenantInterceptor(TenantProperties tenantProperties, TenantProvider tenantProvider) {
|
||||
this.tenantProperties = tenantProperties;
|
||||
this.tenantProvider = tenantProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
if (handler instanceof HandlerMethod handlerMethod) {
|
||||
TenantIgnore tenantIgnore = handlerMethod.getMethodAnnotation(TenantIgnore.class);
|
||||
if (tenantIgnore != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
String tenantId = request.getHeader(tenantProperties.getTenantIdHeader());
|
||||
TenantContext tenantContext = new TenantContext();
|
||||
tenantContext.setTenantId(Convert.toLong(tenantId));
|
||||
TenantContextHolder.setContext(tenantContext);
|
||||
TenantContextHolder.setContext(tenantProvider.getByTenantId(tenantId, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||
|
||||
/**
|
||||
* 多租户 Web MVC 自动配置
|
||||
@@ -35,13 +36,15 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
public class TenantWebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
private final TenantProvider tenantProvider;
|
||||
|
||||
public TenantWebMvcAutoConfiguration(TenantProperties tenantProperties) {
|
||||
public TenantWebMvcAutoConfiguration(TenantProperties tenantProperties, TenantProvider tenantProvider) {
|
||||
this.tenantProperties = tenantProperties;
|
||||
this.tenantProvider = tenantProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new TenantInterceptor(tenantProperties));
|
||||
registry.addInterceptor(new TenantInterceptor(tenantProperties, tenantProvider));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,23 @@
|
||||
|
||||
package top.continew.starter.extension.tenant.config;
|
||||
|
||||
import top.continew.starter.extension.tenant.context.TenantContext;
|
||||
|
||||
/**
|
||||
* 租户数据源提供者
|
||||
* 租户提供者
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author 小熊
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public interface TenantDataSourceProvider {
|
||||
public interface TenantProvider {
|
||||
|
||||
/**
|
||||
* 根据租户 ID 获取数据源配置
|
||||
* 根据租户 ID 获取租户上下文
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @return 数据源配置
|
||||
* @param isVerify 是否验证有效性
|
||||
* @return 租户上下文
|
||||
*/
|
||||
TenantDataSource getByTenantId(String tenantId);
|
||||
TenantContext getByTenantId(String tenantId, boolean isVerify);
|
||||
}
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package top.continew.starter.extension.tenant.context;
|
||||
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||
|
||||
/**
|
||||
* 租户上下文
|
||||
*
|
||||
@@ -29,6 +32,16 @@ public class TenantContext {
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 隔离级别
|
||||
*/
|
||||
private TenantIsolationLevel isolationLevel;
|
||||
|
||||
/**
|
||||
* 数据源信息
|
||||
*/
|
||||
private TenantDataSource dataSource;
|
||||
|
||||
public Long getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
@@ -36,4 +49,20 @@ public class TenantContext {
|
||||
public void setTenantId(Long tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public TenantIsolationLevel getIsolationLevel() {
|
||||
return isolationLevel;
|
||||
}
|
||||
|
||||
public void setIsolationLevel(TenantIsolationLevel isolationLevel) {
|
||||
this.isolationLevel = isolationLevel;
|
||||
}
|
||||
|
||||
public TenantDataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
public void setDataSource(TenantDataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
package top.continew.starter.extension.tenant.context;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -66,4 +70,24 @@ public class TenantContextHolder {
|
||||
public static Long getTenantId() {
|
||||
return Optional.ofNullable(getContext()).map(TenantContext::getTenantId).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户隔离级别
|
||||
*
|
||||
* @return 租户隔离级别
|
||||
*/
|
||||
public static TenantIsolationLevel getIsolationLevel() {
|
||||
return Optional.ofNullable(getContext())
|
||||
.map(TenantContext::getIsolationLevel)
|
||||
.orElse(SpringUtil.getBean(TenantProperties.class).getIsolationLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户数据源
|
||||
*
|
||||
* @return 租户数据源
|
||||
*/
|
||||
public static TenantDataSource getDataSource() {
|
||||
return Optional.ofNullable(getContext()).map(TenantContext::getDataSource).orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 核心模块 -->
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
@@ -30,11 +31,17 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSourceProvider;
|
||||
import top.continew.starter.extension.tenant.handler.*;
|
||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||
import top.continew.starter.extension.tenant.handler.DefaultTenantHandler;
|
||||
import top.continew.starter.extension.tenant.TenantDataSourceHandler;
|
||||
import top.continew.starter.extension.tenant.TenantHandler;
|
||||
import top.continew.starter.extension.tenant.handler.datasource.DefaultTenantDataSourceHandler;
|
||||
import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceAdvisor;
|
||||
import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceInterceptor;
|
||||
import top.continew.starter.extension.tenant.handler.line.DefaultTenantLineHandler;
|
||||
|
||||
/**
|
||||
* 多租户自动配置
|
||||
* 租户自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
@@ -45,89 +52,82 @@ import top.continew.starter.extension.tenant.handler.*;
|
||||
public class TenantAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class);
|
||||
private final TenantProperties tenantProperties;
|
||||
|
||||
private TenantAutoConfiguration() {
|
||||
public TenantAutoConfiguration(TenantProperties tenantProperties) {
|
||||
this.tenantProperties = tenantProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户隔离级别:行级
|
||||
* 租户行级隔离拦截器
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "line", matchIfMissing = true)
|
||||
public static class Line {
|
||||
static {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-Line' completed initialization.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户行级隔离拦截器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
|
||||
return new TenantLineInnerInterceptor(tenantLineHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户行级隔离处理器(默认)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantLineHandler tenantLineHandler(TenantProperties properties) {
|
||||
return new DefaultTenantLineHandler(properties);
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
|
||||
return new TenantLineInnerInterceptor(tenantLineHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户隔离级别:数据源级
|
||||
* 租户行级隔离处理器(默认)
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "datasource")
|
||||
public static class DataSource {
|
||||
static {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-DataSource' completed initialization.");
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantLineHandler tenantLineHandler(TenantProperties properties) {
|
||||
return new DefaultTenantLineHandler(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离通知
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceAdvisor tenantDataSourceAdvisor(TenantDataSourceInterceptor tenantDataSourceInterceptor) {
|
||||
return new TenantDataSourceAdvisor(tenantDataSourceInterceptor);
|
||||
}
|
||||
/**
|
||||
* 租户数据源级隔离通知
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceAdvisor tenantDataSourceAdvisor(TenantDataSourceInterceptor tenantDataSourceInterceptor) {
|
||||
return new TenantDataSourceAdvisor(tenantDataSourceInterceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离拦截器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceInterceptor tenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
|
||||
return new TenantDataSourceInterceptor(tenantDataSourceHandler);
|
||||
}
|
||||
/**
|
||||
* 租户数据源级隔离拦截器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceInterceptor tenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
|
||||
return new TenantDataSourceInterceptor(tenantDataSourceHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离处理器(默认)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceHandler tenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
|
||||
DynamicRoutingDataSource dynamicRoutingDataSource,
|
||||
DefaultDataSourceCreator dataSourceCreator) {
|
||||
return new DefaultTenantDataSourceHandler(tenantDataSourceProvider, dynamicRoutingDataSource, dataSourceCreator);
|
||||
}
|
||||
/**
|
||||
* 租户数据源级隔离处理器(默认)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceHandler tenantDataSourceHandler(javax.sql.DataSource dataSource,
|
||||
DefaultDataSourceCreator dataSourceCreator) {
|
||||
return new DefaultTenantDataSourceHandler((DynamicRoutingDataSource)dataSource, dataSourceCreator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多租户数据源提供者
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceProvider tenantDataSourceProvider() {
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
||||
.forClass(TenantDataSourceProvider.class));
|
||||
}
|
||||
throw new NoSuchBeanDefinitionException(TenantDataSourceProvider.class);
|
||||
/**
|
||||
* 租户提供者
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantProvider tenantProvider() {
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
||||
.forClass(TenantProvider.class));
|
||||
}
|
||||
throw new NoSuchBeanDefinitionException(TenantProvider.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户处理器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantHandler tenantHandler(TenantDataSourceHandler tenantDataSourceHandler, TenantProvider tenantProvider) {
|
||||
return new DefaultTenantHandler(tenantProperties, tenantDataSourceHandler, tenantProvider);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant' completed initialization.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.tenant.handler;
|
||||
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import top.continew.starter.extension.tenant.TenantDataSourceHandler;
|
||||
import top.continew.starter.extension.tenant.TenantHandler;
|
||||
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||
import top.continew.starter.extension.tenant.context.TenantContext;
|
||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||
|
||||
/**
|
||||
* 租户处理器
|
||||
*
|
||||
* @author 小熊
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public class DefaultTenantHandler implements TenantHandler {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
private final TenantDataSourceHandler dataSourceHandler;
|
||||
private final TenantProvider tenantProvider;
|
||||
|
||||
public DefaultTenantHandler(TenantProperties tenantProperties,
|
||||
TenantDataSourceHandler dataSourceHandler,
|
||||
TenantProvider tenantProvider) {
|
||||
this.tenantProperties = tenantProperties;
|
||||
this.dataSourceHandler = dataSourceHandler;
|
||||
this.tenantProvider = tenantProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Long tenantId, Runnable runnable) {
|
||||
if (!tenantProperties.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
TenantContext tenantHandler = tenantProvider.getByTenantId(tenantId.toString(), false);
|
||||
// 保存当前的租户上下文
|
||||
TenantContext originalContext = TenantContextHolder.getContext();
|
||||
boolean isPush = false;
|
||||
try {
|
||||
// 设置新的租户上下文
|
||||
TenantContextHolder.setContext(tenantHandler);
|
||||
// 切换数据源
|
||||
if (TenantIsolationLevel.DATASOURCE.equals(tenantHandler.getIsolationLevel())) {
|
||||
dataSourceHandler.changeDataSource(tenantHandler.getDataSource());
|
||||
isPush = true;
|
||||
}
|
||||
// 执行业务逻辑
|
||||
runnable.run();
|
||||
} finally {
|
||||
// 恢复原始的租户上下文
|
||||
if (originalContext != null) {
|
||||
TenantContextHolder.setContext(originalContext);
|
||||
} else {
|
||||
TenantContextHolder.clearContext();
|
||||
}
|
||||
if (isPush) {
|
||||
DynamicDataSourceContextHolder.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.tenant.handler;
|
||||
package top.continew.starter.extension.tenant.handler.datasource;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
@@ -24,7 +24,7 @@ import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSourceProvider;
|
||||
import top.continew.starter.extension.tenant.TenantDataSourceHandler;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -39,24 +39,20 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(DefaultTenantDataSourceHandler.class);
|
||||
private final DynamicRoutingDataSource dynamicRoutingDataSource;
|
||||
private final DefaultDataSourceCreator dataSourceCreator;
|
||||
private final TenantDataSourceProvider tenantDataSourceProvider;
|
||||
|
||||
public DefaultTenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
|
||||
DynamicRoutingDataSource dynamicRoutingDataSource,
|
||||
public DefaultTenantDataSourceHandler(DynamicRoutingDataSource dynamicRoutingDataSource,
|
||||
DefaultDataSourceCreator dataSourceCreator) {
|
||||
this.tenantDataSourceProvider = tenantDataSourceProvider;
|
||||
this.dynamicRoutingDataSource = dynamicRoutingDataSource;
|
||||
this.dataSourceCreator = dataSourceCreator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeDataSource(String dataSourceName) {
|
||||
public void changeDataSource(TenantDataSource tenantDataSource) {
|
||||
if (tenantDataSource == null) {
|
||||
return;
|
||||
}
|
||||
String dataSourceName = tenantDataSource.getPoolName();
|
||||
if (!this.containsDataSource(dataSourceName)) {
|
||||
TenantDataSource tenantDataSource = tenantDataSourceProvider.getByTenantId(dataSourceName);
|
||||
if (null == tenantDataSource) {
|
||||
throw new IllegalArgumentException("Data source [%s] configuration not found"
|
||||
.formatted(dataSourceName));
|
||||
}
|
||||
DataSource datasource = this.createDataSource(tenantDataSource);
|
||||
dynamicRoutingDataSource.addDataSource(dataSourceName, datasource);
|
||||
log.info("Load data source: {}", dataSourceName);
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.tenant.handler;
|
||||
package top.continew.starter.extension.tenant.handler.datasource;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.springframework.aop.Pointcut;
|
||||
@@ -65,7 +65,10 @@ public class TenantDataSourceAdvisor extends AbstractPointcutAdvisor implements
|
||||
*/
|
||||
private Pointcut buildPointcut() {
|
||||
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
|
||||
cut.setExpression("!@annotation(top.continew.starter.extension.tenant.annotation.TenantDataSourceIgnore)");
|
||||
cut.setExpression("""
|
||||
execution(* *..controller..*(..))
|
||||
&& !@annotation(top.continew.starter.extension.tenant.annotation.TenantIgnore)
|
||||
""");
|
||||
return new ComposablePointcut((Pointcut)cut);
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.tenant.handler;
|
||||
package top.continew.starter.extension.tenant.handler.datasource;
|
||||
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||
import top.continew.starter.extension.tenant.TenantDataSourceHandler;
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离拦截器
|
||||
@@ -37,15 +39,13 @@ public class TenantDataSourceInterceptor implements MethodInterceptor {
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (null == tenantId) {
|
||||
if (TenantIsolationLevel.LINE.equals(TenantContextHolder.getIsolationLevel())) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
// 切换数据源
|
||||
boolean isPush = false;
|
||||
try {
|
||||
String dataSourceName = tenantId.toString();
|
||||
tenantDataSourceHandler.changeDataSource(dataSourceName);
|
||||
tenantDataSourceHandler.changeDataSource(TenantContextHolder.getDataSource());
|
||||
isPush = true;
|
||||
return invocation.proceed();
|
||||
} finally {
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.tenant.handler;
|
||||
package top.continew.starter.extension.tenant.handler.line;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
@@ -22,6 +22,7 @@ import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||
|
||||
/**
|
||||
* 默认租户行级隔离处理器
|
||||
@@ -57,6 +58,9 @@ public class DefaultTenantLineHandler implements TenantLineHandler {
|
||||
if (null != tenantId && tenantId.equals(tenantProperties.getSuperTenantId())) {
|
||||
return true;
|
||||
}
|
||||
if (TenantIsolationLevel.DATASOURCE.equals(TenantContextHolder.getIsolationLevel())) {
|
||||
return true;
|
||||
}
|
||||
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package top.continew.starter.file.excel.converter;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import com.alibaba.excel.converters.Converter;
|
||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
||||
@@ -34,7 +32,7 @@ import top.continew.starter.core.enums.BaseEnum;
|
||||
* @see BaseEnum
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ExcelBaseEnumConverter implements Converter<BaseEnum<Integer>> {
|
||||
public class ExcelBaseEnumConverter implements Converter<BaseEnum<?>> {
|
||||
|
||||
@Override
|
||||
public Class<BaseEnum> supportJavaTypeKey() {
|
||||
@@ -50,17 +48,17 @@ public class ExcelBaseEnumConverter implements Converter<BaseEnum<Integer>> {
|
||||
* 转换为 Java 数据(读取 Excel)
|
||||
*/
|
||||
@Override
|
||||
public BaseEnum convertToJavaData(ReadCellData<?> cellData,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
return this.getEnum(BaseEnum.class, Convert.toStr(cellData.getData()));
|
||||
public BaseEnum<?> convertToJavaData(ReadCellData<?> cellData,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
return BaseEnum.getByDescription(cellData.getStringValue(), contentProperty.getField().getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 Excel 数据(写入 Excel)
|
||||
*/
|
||||
@Override
|
||||
public WriteCellData<String> convertToExcelData(BaseEnum<Integer> value,
|
||||
public WriteCellData<String> convertToExcelData(BaseEnum<?> value,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
if (null == value) {
|
||||
@@ -68,24 +66,4 @@ public class ExcelBaseEnumConverter implements Converter<BaseEnum<Integer>> {
|
||||
}
|
||||
return new WriteCellData<>(value.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 value 获取枚举对象,获取不到时为 {@code null}
|
||||
*
|
||||
* @param enumType 枚举类型
|
||||
* @param description 描述
|
||||
* @return 对应枚举 ,获取不到时为 {@code null}
|
||||
*/
|
||||
private BaseEnum<Integer> getEnum(Class<?> enumType, String description) {
|
||||
Object[] enumConstants = enumType.getEnumConstants();
|
||||
for (Object enumConstant : enumConstants) {
|
||||
if (ClassUtil.isAssignable(BaseEnum.class, enumType)) {
|
||||
BaseEnum<Integer> baseEnum = (BaseEnum<Integer>)enumConstant;
|
||||
if (baseEnum.getDescription().equals(description)) {
|
||||
return baseEnum;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
22
continew-starter-log/continew-starter-log-aop/pom.xml
Normal file
22
continew-starter-log/continew-starter-log-aop/pom.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-aop</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - 基于 AOP 实现</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!-- 日志模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 访问日志切面
|
||||
*
|
||||
* @author echo
|
||||
* @author Charles7c
|
||||
* @since 2.8.0
|
||||
*/
|
||||
@Aspect
|
||||
public class AccessLogAspect {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AccessLogAspect.class);
|
||||
private final LogProperties logProperties;
|
||||
|
||||
public AccessLogAspect(LogProperties logProperties) {
|
||||
this.logProperties = logProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切点 - 匹配所有控制器层的 GET 请求方法
|
||||
*/
|
||||
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
|
||||
public void pointcut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 切点 - 匹配所有控制器层的 GET 请求方法
|
||||
*/
|
||||
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
|
||||
public void pointcutGet() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 切点 - 匹配所有控制器层的 POST 请求方法
|
||||
*/
|
||||
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
|
||||
public void pointcutPost() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 切点 - 匹配所有控制器层的 PUT 请求方法
|
||||
*/
|
||||
@Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
|
||||
public void pointcutPut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 切点 - 匹配所有控制器层的 DELETE 请求方法
|
||||
*/
|
||||
@Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
|
||||
public void pointcutDelete() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 切点 - 匹配所有控制器层的 PATCH 请求方法
|
||||
*/
|
||||
@Pointcut("@annotation(org.springframework.web.bind.annotation.PatchMapping)")
|
||||
public void pointcutPatch() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印访问日志
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @return 返回结果
|
||||
* @throws Throwable 异常
|
||||
*/
|
||||
@Around("pointcut() || pointcutGet() || pointcutPost() || pointcutPut() || pointcutDelete() || pointcutPatch()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Instant startTime = Instant.now();
|
||||
// 非 Web 环境不记录
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
HttpServletResponse response = attributes.getResponse();
|
||||
try {
|
||||
// 打印请求日志
|
||||
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
||||
log.info("[{}] {}", request.getMethod(), request.getRequestURI());
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
Instant endTime = Instant.now();
|
||||
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
||||
Duration timeTaken = Duration.between(startTime, endTime);
|
||||
log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), response != null
|
||||
? response.getStatus()
|
||||
: "N/A", timeTaken.toMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import top.continew.starter.log.annotation.Log;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.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;
|
||||
|
||||
/**
|
||||
* 日志切面
|
||||
*
|
||||
* @author echo
|
||||
* @author Charles7c
|
||||
* @since 2.8.0
|
||||
*/
|
||||
@Aspect
|
||||
public class LogAspect {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
|
||||
private final LogProperties logProperties;
|
||||
private final LogHandler logHandler;
|
||||
private final LogDao logDao;
|
||||
|
||||
public LogAspect(LogProperties logProperties, LogHandler logHandler, LogDao logDao) {
|
||||
this.logProperties = logProperties;
|
||||
this.logHandler = logHandler;
|
||||
this.logDao = logDao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切点 - 匹配日志注解 {@link Log}
|
||||
*/
|
||||
@Pointcut("@annotation(top.continew.starter.log.annotation.Log)")
|
||||
public void pointcut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @return 返回结果
|
||||
* @throws Throwable 异常
|
||||
*/
|
||||
@Around("pointcut()")
|
||||
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)) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
String errorMsg = null;
|
||||
// 开始记录
|
||||
LogRecord.Started startedLogRecord = logHandler.start(startTime, request);
|
||||
try {
|
||||
// 执行目标方法
|
||||
return joinPoint.proceed();
|
||||
} catch (Exception e) {
|
||||
errorMsg = CharSequenceUtil.sub(e.getMessage(), 0, 2000);
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
Instant endTime = Instant.now();
|
||||
HttpServletResponse response = SpringWebUtils.getResponse();
|
||||
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties
|
||||
.getIncludes(), targetMethod, targetClass);
|
||||
// 记录异常信息
|
||||
if (errorMsg != null) {
|
||||
logRecord.setErrorMsg(errorMsg);
|
||||
}
|
||||
logDao.add(logRecord);
|
||||
} catch (Exception e) {
|
||||
log.error("Logging http log occurred an error: {}.", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否要记录日志
|
||||
*
|
||||
* @param targetMethod 目标方法
|
||||
* @param targetClass 目标类
|
||||
* @return true:需要记录;false:不需要记录
|
||||
*/
|
||||
private boolean isRequestRecord(Method targetMethod, Class<?> targetClass) {
|
||||
// 非 Web 环境不记录
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null || attributes.getResponse() == null) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口匹配排除列表,不记录日志
|
||||
if (logProperties.isMatch(attributes.getRequest().getRequestURI())) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口方法或类上有 @Log 注解,且要求忽略该接口,则不记录日志
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
if (null != methodLog && methodLog.ignore()) {
|
||||
return false;
|
||||
}
|
||||
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
|
||||
return null == classLog || !classLog.ignore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @return 方法
|
||||
*/
|
||||
private Method getMethod(JoinPoint joinPoint) {
|
||||
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
|
||||
return signature.getMethod();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import top.continew.starter.log.annotation.ConditionalOnEnabledLog;
|
||||
import top.continew.starter.log.aspect.AccessLogAspect;
|
||||
import top.continew.starter.log.aspect.LogAspect;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.dao.impl.DefaultLogDaoImpl;
|
||||
import top.continew.starter.log.handler.AopLogHandler;
|
||||
import top.continew.starter.log.LogFilter;
|
||||
import top.continew.starter.log.LogHandler;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
|
||||
/**
|
||||
* 日志自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author echo
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnEnabledLog
|
||||
@EnableConfigurationProperties(LogProperties.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
public class LogAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LogAutoConfiguration.class);
|
||||
private final LogProperties logProperties;
|
||||
|
||||
public LogAutoConfiguration(LogProperties logProperties) {
|
||||
this.logProperties = logProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志过滤器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogFilter logFilter() {
|
||||
return new LogFilter(logProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志切面
|
||||
*
|
||||
* @param logHandler 日志处理器
|
||||
* @param logDao 日志持久层接口
|
||||
* @return {@link LogAspect }
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogAspect logAspect(LogHandler logHandler, LogDao logDao) {
|
||||
return new LogAspect(logProperties, logHandler, logDao);
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问日志切面
|
||||
*
|
||||
* @return {@link LogAspect }
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AccessLogAspect accessLogAspect() {
|
||||
return new AccessLogAspect(logProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志处理器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogHandler logHandler() {
|
||||
return new AopLogHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志持久层接口
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogDao logDao() {
|
||||
return new DefaultLogDaoImpl();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Log-AOP' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -14,19 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.crud.model.req;
|
||||
package top.continew.starter.log.handler;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import top.continew.starter.log.AbstractLogHandler;
|
||||
|
||||
/**
|
||||
* 请求参数基类
|
||||
* 日志处理器-AOP 版实现
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public class BaseReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
public class AopLogHandler extends AbstractLogHandler {
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.starter.log.autoconfigure.LogAutoConfiguration
|
||||
@@ -11,4 +11,12 @@
|
||||
|
||||
<artifactId>continew-starter-log-core</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Swagger 注解 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
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 top.continew.starter.log.annotation.Log;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
|
||||
import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志处理器基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public abstract class AbstractLogHandler implements LogHandler {
|
||||
|
||||
@Override
|
||||
public LogRecord.Started start(Instant startTime, HttpServletRequest request) {
|
||||
return LogRecord.start(startTime, new RecordableServletHttpRequest(request));
|
||||
}
|
||||
|
||||
@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);
|
||||
// 记录日志描述
|
||||
if (includeSet.contains(Include.DESCRIPTION)) {
|
||||
this.logDescription(logRecord, targetMethod);
|
||||
}
|
||||
// 记录所属模块
|
||||
if (includeSet.contains(Include.MODULE)) {
|
||||
this.logModule(logRecord, targetMethod, targetClass);
|
||||
}
|
||||
return logRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogRecord finish(LogRecord.Started started,
|
||||
Instant endTime,
|
||||
HttpServletResponse response,
|
||||
Set<Include> includes) {
|
||||
return started.finish(endTime, new RecordableServletHttpResponse(response, response.getStatus()), includes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志描述
|
||||
*
|
||||
* @param logRecord 日志记录
|
||||
* @param targetMethod 目标方法
|
||||
*/
|
||||
@Override
|
||||
public void logDescription(LogRecord logRecord, Method targetMethod) {
|
||||
logRecord.setDescription("请在该接口方法上添加 @top.continew.starter.log.annotation.Log(value) 来指定日志描述");
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
// 例如:@Log("新增部门") -> 新增部门
|
||||
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.value())) {
|
||||
logRecord.setDescription(methodLog.value());
|
||||
return;
|
||||
}
|
||||
// 例如:@Operation(summary="新增部门") -> 新增部门
|
||||
Operation methodOperation = AnnotationUtil.getAnnotation(targetMethod, Operation.class);
|
||||
if (null != methodOperation && CharSequenceUtil.isNotBlank(methodOperation.summary())) {
|
||||
logRecord.setDescription(methodOperation.summary());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录所属模块
|
||||
*
|
||||
* @param logRecord 日志记录
|
||||
* @param targetMethod 目标方法
|
||||
* @param targetClass 目标类
|
||||
*/
|
||||
@Override
|
||||
public void logModule(LogRecord logRecord, Method targetMethod, Class<?> targetClass) {
|
||||
logRecord.setModule("请在该接口方法或类上添加 @top.continew.starter.log.annotation.Log(module) 来指定所属模块");
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
// 例如:@Log(module = "部门管理") -> 部门管理
|
||||
// 方法级注解优先级高于类级注解
|
||||
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.module())) {
|
||||
logRecord.setModule(methodLog.module());
|
||||
return;
|
||||
}
|
||||
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
|
||||
if (null != classLog && CharSequenceUtil.isNotBlank(classLog.module())) {
|
||||
logRecord.setModule(classLog.module());
|
||||
return;
|
||||
}
|
||||
// 例如:@Tag(name = "部门管理") -> 部门管理
|
||||
Tag classTag = AnnotationUtil.getAnnotation(targetClass, Tag.class);
|
||||
if (null != classTag && CharSequenceUtil.isNotBlank(classTag.name())) {
|
||||
logRecord.setModule(classTag.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Include> getIncludes(Set<Include> includes, Method targetMethod, Class<?> targetClass) {
|
||||
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
|
||||
Set<Include> includeSet = new HashSet<>(includes);
|
||||
if (null != classLog) {
|
||||
this.processInclude(includeSet, classLog);
|
||||
}
|
||||
// 方法级注解优先级高于类级注解
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
if (null != methodLog) {
|
||||
this.processInclude(includeSet, methodLog);
|
||||
}
|
||||
return includeSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日志包含信息
|
||||
*
|
||||
* @param includes 日志包含信息
|
||||
* @param logAnnotation Log 注解
|
||||
*/
|
||||
private void processInclude(Set<Include> includes, Log logAnnotation) {
|
||||
Include[] includeArr = logAnnotation.includes();
|
||||
if (includeArr.length > 0) {
|
||||
includes.addAll(Set.of(includeArr));
|
||||
}
|
||||
Include[] excludeArr = logAnnotation.excludes();
|
||||
if (excludeArr.length > 0) {
|
||||
includes.removeAll(Set.of(excludeArr));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
package top.continew.starter.log;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.servlet.FilterChain;
|
||||
@@ -28,7 +28,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public interface LogHandler {
|
||||
|
||||
/**
|
||||
* 开始日志记录
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param request 请求对象
|
||||
* @return 日志记录器
|
||||
*/
|
||||
LogRecord.Started start(Instant startTime, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 结束日志记录
|
||||
*
|
||||
* @param started 开始日志记录器
|
||||
* @param endTime 结束时间
|
||||
* @param response 响应对象
|
||||
* @param includes 包含信息
|
||||
* @return 日志记录
|
||||
*/
|
||||
LogRecord finish(LogRecord.Started started, Instant endTime, HttpServletResponse response, Set<Include> includes);
|
||||
|
||||
/**
|
||||
* 结束日志记录
|
||||
*
|
||||
* @param started 开始日志记录器-
|
||||
* @param endTime 结束时间
|
||||
* @param response 响应对象
|
||||
* @param includes 包含信息
|
||||
* @param targetMethod 目标方法
|
||||
* @param targetClass 目标类
|
||||
* @return 日志记录
|
||||
*/
|
||||
LogRecord finish(LogRecord.Started started,
|
||||
Instant endTime,
|
||||
HttpServletResponse response,
|
||||
Set<Include> includes,
|
||||
Method targetMethod,
|
||||
Class<?> targetClass);
|
||||
|
||||
/**
|
||||
* 记录日志描述
|
||||
*
|
||||
* @param logRecord 日志记录
|
||||
* @param targetMethod 目标方法
|
||||
*/
|
||||
void logDescription(LogRecord logRecord, Method targetMethod);
|
||||
|
||||
/**
|
||||
* 记录所属模块
|
||||
*
|
||||
* @param logRecord 日志记录
|
||||
* @param targetMethod 目标方法
|
||||
* @param targetClass 目标类
|
||||
*/
|
||||
void logModule(LogRecord logRecord, Method targetMethod, Class<?> targetClass);
|
||||
|
||||
/**
|
||||
* 获取日志包含信息
|
||||
*
|
||||
* @param includes 默认包含信息
|
||||
* @param targetMethod 目标方法
|
||||
* @param targetClass 目标类
|
||||
* @return 日志包含信息
|
||||
*/
|
||||
Set<Include> getIncludes(Set<Include> includes, Method targetMethod, Class<?> targetClass);
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
package top.continew.starter.log.annotation;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.annotation;
|
||||
package top.continew.starter.log.annotation;
|
||||
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.dao;
|
||||
package top.continew.starter.log.dao;
|
||||
|
||||
import top.continew.starter.log.core.model.LogRecord;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.dao.impl;
|
||||
package top.continew.starter.log.dao.impl;
|
||||
|
||||
import top.continew.starter.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.model.LogRecord;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -30,7 +30,7 @@ import java.util.List;
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class LogDaoDefaultImpl implements LogDao {
|
||||
public class DefaultLogDaoImpl implements LogDao {
|
||||
|
||||
/**
|
||||
* 容量
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.enums;
|
||||
package top.continew.starter.log.enums;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.model;
|
||||
package top.continew.starter.log.http;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.model;
|
||||
package top.continew.starter.log.http;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
package top.continew.starter.log.http.servlet;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -25,7 +25,7 @@ import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.log.core.model.RecordableHttpRequest;
|
||||
import top.continew.starter.log.http.RecordableHttpRequest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
package top.continew.starter.log.http.servlet;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -22,7 +22,7 @@ import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.log.core.model.RecordableHttpResponse;
|
||||
import top.continew.starter.log.http.RecordableHttpResponse;
|
||||
import top.continew.starter.web.util.ServletUtils;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
package top.continew.starter.log.model;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.web.util.SpringWebUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -14,9 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.model;
|
||||
package top.continew.starter.log.model;
|
||||
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
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;
|
||||
@@ -63,6 +65,11 @@ public class LogRecord {
|
||||
*/
|
||||
private final Instant timestamp;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMsg;
|
||||
|
||||
public LogRecord(Instant timestamp, LogRequest request, LogResponse response, Duration timeTaken) {
|
||||
this.timestamp = timestamp;
|
||||
this.request = request;
|
||||
@@ -108,9 +115,9 @@ public class LogRecord {
|
||||
/**
|
||||
* 结束日志记录
|
||||
*
|
||||
* @param clock 时间
|
||||
* @param response 响应信息
|
||||
* @param includes 包含信息
|
||||
* @param timestamp 结束时间
|
||||
* @param response 响应信息
|
||||
* @param includes 包含信息
|
||||
* @return 日志记录
|
||||
*/
|
||||
public LogRecord finish(Instant timestamp, RecordableHttpResponse response, Set<Include> includes) {
|
||||
@@ -164,4 +171,12 @@ public class LogRecord {
|
||||
public Instant getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setErrorMsg(String errorMsg) {
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
public String getErrorMsg() {
|
||||
return errorMsg;
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.model;
|
||||
package top.continew.starter.log.model;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import top.continew.starter.core.util.ExceptionUtils;
|
||||
import top.continew.starter.core.util.IpUtils;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.log.http.RecordableHttpRequest;
|
||||
import top.continew.starter.web.util.ServletUtils;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -14,9 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.core.model;
|
||||
package top.continew.starter.log.model;
|
||||
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.log.http.RecordableHttpResponse;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -10,15 +10,9 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - 拦截器版(Spring Boot Actuator HttpTrace 增强版)</description>
|
||||
<description>ContiNew Starter 日志模块 - 基于拦截器实现(Spring Boot Actuator HttpTrace 增强版)</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Swagger 注解 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- TTL(线程间传递 ThreadLocal,异步执行时上下文传递的解决方案) -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
package top.continew.starter.log.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
@@ -26,10 +26,14 @@ 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.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.dao.impl.LogDaoDefaultImpl;
|
||||
import top.continew.starter.log.interceptor.handler.LogFilter;
|
||||
import top.continew.starter.log.interceptor.handler.LogInterceptor;
|
||||
import top.continew.starter.log.annotation.ConditionalOnEnabledLog;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.dao.impl.DefaultLogDaoImpl;
|
||||
import top.continew.starter.log.handler.InterceptorLogHandler;
|
||||
import top.continew.starter.log.LogFilter;
|
||||
import top.continew.starter.log.LogHandler;
|
||||
import top.continew.starter.log.interceptor.LogInterceptor;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
|
||||
/**
|
||||
* 日志自动配置
|
||||
@@ -52,7 +56,7 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LogInterceptor(logDao(), logProperties));
|
||||
registry.addInterceptor(new LogInterceptor(logProperties, logHandler(), logDao()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,17 +68,26 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
return new LogFilter(logProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志处理器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogHandler logHandler() {
|
||||
return new InterceptorLogHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志持久层接口
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogDao logDao() {
|
||||
return new LogDaoDefaultImpl();
|
||||
return new DefaultLogDaoImpl();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Log' completed initialization.");
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Log-Interceptor' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -14,21 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.crud.constant;
|
||||
package top.continew.starter.log.handler;
|
||||
|
||||
import top.continew.starter.log.AbstractLogHandler;
|
||||
|
||||
/**
|
||||
* 数据源容器相关常量(Crane4j 数据填充组件使用)
|
||||
* 日志处理器-拦截器版实现
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public class ContainerPool {
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
public static final String USER_NICKNAME = "UserNickname";
|
||||
|
||||
protected ContainerPool() {
|
||||
}
|
||||
}
|
||||
public class InterceptorLogHandler extends AbstractLogHandler {
|
||||
}
|
||||
@@ -14,13 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
package top.continew.starter.log.interceptor;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
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;
|
||||
@@ -28,16 +26,15 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import top.continew.starter.log.core.annotation.Log;
|
||||
import top.continew.starter.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.core.model.LogRecord;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
import top.continew.starter.log.annotation.Log;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.LogHandler;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志拦截器
|
||||
@@ -48,14 +45,16 @@ import java.util.Set;
|
||||
public class LogInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LogInterceptor.class);
|
||||
private final LogDao logDao;
|
||||
private final LogProperties logProperties;
|
||||
private final LogHandler logHandler;
|
||||
private final LogDao logDao;
|
||||
private final TransmittableThreadLocal<Instant> timeTtl = new TransmittableThreadLocal<>();
|
||||
private final TransmittableThreadLocal<LogRecord.Started> logTtl = new TransmittableThreadLocal<>();
|
||||
|
||||
public LogInterceptor(LogDao logDao, LogProperties logProperties) {
|
||||
this.logDao = logDao;
|
||||
public LogInterceptor(LogProperties logProperties, LogHandler logHandler, LogDao logDao) {
|
||||
this.logProperties = logProperties;
|
||||
this.logHandler = logHandler;
|
||||
this.logDao = logDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,8 +66,9 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
log.info("[{}] {}", request.getMethod(), request.getRequestURI());
|
||||
timeTtl.set(startTime);
|
||||
}
|
||||
// 开始日志记录
|
||||
if (this.isRequestRecord(handler, request)) {
|
||||
LogRecord.Started startedLogRecord = LogRecord.start(startTime, new RecordableServletHttpRequest(request));
|
||||
LogRecord.Started startedLogRecord = logHandler.start(startTime, request);
|
||||
logTtl.set(startedLogRecord);
|
||||
}
|
||||
return true;
|
||||
@@ -90,110 +90,22 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
if (null == startedLogRecord) {
|
||||
return;
|
||||
}
|
||||
// 结束日志记录
|
||||
HandlerMethod handlerMethod = (HandlerMethod)handler;
|
||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
||||
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
|
||||
Set<Include> includeSet = this.getIncludes(methodLog, classLog);
|
||||
LogRecord finishedLogRecord = startedLogRecord
|
||||
.finish(endTime, new RecordableServletHttpResponse(response, response.getStatus()), includeSet);
|
||||
// 记录日志描述
|
||||
if (includeSet.contains(Include.DESCRIPTION)) {
|
||||
this.logDescription(finishedLogRecord, methodLog, handlerMethod);
|
||||
}
|
||||
// 记录所属模块
|
||||
if (includeSet.contains(Include.MODULE)) {
|
||||
this.logModule(finishedLogRecord, methodLog, classLog, handlerMethod);
|
||||
}
|
||||
logDao.add(finishedLogRecord);
|
||||
Method targetMethod = handlerMethod.getMethod();
|
||||
Class<?> targetClass = handlerMethod.getBeanType();
|
||||
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties
|
||||
.getIncludes(), targetMethod, targetClass);
|
||||
logDao.add(logRecord);
|
||||
} catch (Exception ex) {
|
||||
log.error("Logging http log occurred an error: {}.", ex.getMessage(), ex);
|
||||
throw ex;
|
||||
} finally {
|
||||
timeTtl.remove();
|
||||
logTtl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志包含信息
|
||||
*
|
||||
* @param methodLog 方法级 Log 注解
|
||||
* @param classLog 类级 Log 注解
|
||||
* @return 日志包含信息
|
||||
*/
|
||||
private Set<Include> getIncludes(Log methodLog, Log classLog) {
|
||||
Set<Include> includeSet = new HashSet<>(logProperties.getIncludes());
|
||||
if (null != classLog) {
|
||||
this.processInclude(includeSet, classLog);
|
||||
}
|
||||
if (null != methodLog) {
|
||||
this.processInclude(includeSet, methodLog);
|
||||
}
|
||||
return includeSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日志包含信息
|
||||
*
|
||||
* @param includes 日志包含信息
|
||||
* @param logAnnotation Log 注解
|
||||
*/
|
||||
private void processInclude(Set<Include> includes, Log logAnnotation) {
|
||||
Include[] includeArr = logAnnotation.includes();
|
||||
if (includeArr.length > 0) {
|
||||
includes.addAll(Set.of(includeArr));
|
||||
}
|
||||
Include[] excludeArr = logAnnotation.excludes();
|
||||
if (excludeArr.length > 0) {
|
||||
includes.removeAll(Set.of(excludeArr));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录描述
|
||||
*
|
||||
* @param logRecord 日志信息
|
||||
* @param methodLog 方法级 Log 注解
|
||||
* @param handlerMethod 处理器方法
|
||||
*/
|
||||
private void logDescription(LogRecord logRecord, Log methodLog, HandlerMethod handlerMethod) {
|
||||
// 例如:@Log("新增部门") -> 新增部门
|
||||
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.value())) {
|
||||
logRecord.setDescription(methodLog.value());
|
||||
return;
|
||||
}
|
||||
// 例如:@Operation(summary="新增部门") -> 新增部门
|
||||
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
|
||||
if (null != methodOperation) {
|
||||
logRecord.setDescription(CharSequenceUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录模块
|
||||
*
|
||||
* @param logRecord 日志信息
|
||||
* @param methodLog 方法级 Log 注解
|
||||
* @param classLog 类级 Log 注解
|
||||
* @param handlerMethod 处理器方法
|
||||
*/
|
||||
private void logModule(LogRecord logRecord, Log methodLog, Log classLog, HandlerMethod handlerMethod) {
|
||||
// 例如:@Log(module = "部门管理") -> 部门管理
|
||||
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.module())) {
|
||||
logRecord.setModule(methodLog.module());
|
||||
return;
|
||||
}
|
||||
if (null != classLog && CharSequenceUtil.isNotBlank(classLog.module())) {
|
||||
logRecord.setModule(classLog.module());
|
||||
return;
|
||||
}
|
||||
// 例如:@Tag(name = "部门管理") -> 部门管理
|
||||
Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class);
|
||||
if (null != classTag) {
|
||||
String name = classTag.name();
|
||||
logRecord.setModule(CharSequenceUtil.blankToDefault(name, "请在该接口类上指定所属模块"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否要记录日志
|
||||
*
|
||||
@@ -1 +1 @@
|
||||
top.continew.starter.log.interceptor.autoconfigure.LogAutoConfiguration
|
||||
top.continew.starter.log.autoconfigure.LogAutoConfiguration
|
||||
@@ -16,6 +16,7 @@
|
||||
<modules>
|
||||
<module>continew-starter-log-core</module>
|
||||
<module>continew-starter-log-interceptor</module>
|
||||
<module>continew-starter-log-aop</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -37,9 +37,9 @@ public enum Algorithm {
|
||||
DES(DesEncryptor.class),
|
||||
|
||||
/**
|
||||
* PBEWithMD5AndDES
|
||||
* PBE With MD5 And DES
|
||||
*/
|
||||
PBEWithMD5AndDES(PbeWithMd5AndDesEncryptor.class),
|
||||
PBE_WITH_MD5_AND_DES(PbeWithMd5AndDesEncryptor.class),
|
||||
|
||||
/**
|
||||
* RSA
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
package top.continew.starter.security.limiter.annotation;
|
||||
|
||||
import org.redisson.api.RateIntervalUnit;
|
||||
import top.continew.starter.security.limiter.enums.LimitType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 限流注解
|
||||
@@ -60,7 +60,7 @@ public @interface RateLimiter {
|
||||
/**
|
||||
* 速率间隔时间单位(默认:毫秒)
|
||||
*/
|
||||
RateIntervalUnit unit() default RateIntervalUnit.MILLISECONDS;
|
||||
TimeUnit unit() default TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
|
||||
@@ -39,6 +39,7 @@ import top.continew.starter.security.limiter.exception.RateLimiterException;
|
||||
import top.continew.starter.web.util.SpringWebUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -129,12 +130,11 @@ public class RateLimiterAspect {
|
||||
// 限流器配置
|
||||
RateType rateType = rateLimiter.type() == LimitType.CLUSTER ? RateType.PER_CLIENT : RateType.OVERALL;
|
||||
int rate = rateLimiter.rate();
|
||||
int rateInterval = rateLimiter.interval();
|
||||
RateIntervalUnit rateIntervalUnit = rateLimiter.unit();
|
||||
Duration rateInterval = Duration.ofMillis(rateLimiter.unit().toMillis(rateLimiter.interval()));
|
||||
// 判断是否需要更新限流器
|
||||
if (this.isConfigurationUpdateNeeded(rRateLimiter, rateType, rate, rateInterval, rateIntervalUnit)) {
|
||||
if (this.isConfigurationUpdateNeeded(rRateLimiter, rateType, rate, rateInterval)) {
|
||||
// 更新限流器
|
||||
rRateLimiter.setRate(rateType, rate, rateInterval, rateIntervalUnit);
|
||||
rRateLimiter.setRate(rateType, rate, rateInterval);
|
||||
}
|
||||
// 尝试获取令牌
|
||||
return !rRateLimiter.tryAcquire();
|
||||
@@ -181,20 +181,18 @@ public class RateLimiterAspect {
|
||||
/**
|
||||
* 判断是否需要更新限流器配置
|
||||
*
|
||||
* @param rRateLimiter 限流器
|
||||
* @param rateType 限流类型(OVERALL:全局限流;PER_CLIENT:单机限流)
|
||||
* @param rate 速率(指定时间间隔产生的令牌数)
|
||||
* @param rateInterval 速率间隔
|
||||
* @param rateIntervalUnit 时间单位
|
||||
* @param rRateLimiter 限流器
|
||||
* @param rateType 限流类型(OVERALL:全局限流;PER_CLIENT:单机限流)
|
||||
* @param rate 速率(指定时间间隔产生的令牌数)
|
||||
* @param rateInterval 速率间隔
|
||||
* @return 是否需要更新配置
|
||||
*/
|
||||
private boolean isConfigurationUpdateNeeded(RRateLimiter rRateLimiter,
|
||||
RateType rateType,
|
||||
long rate,
|
||||
long rateInterval,
|
||||
RateIntervalUnit rateIntervalUnit) {
|
||||
Duration rateInterval) {
|
||||
RateLimiterConfig config = rRateLimiter.getConfig();
|
||||
return !Objects.equals(config.getRateType(), rateType) || !Objects.equals(config.getRate(), rate) || !Objects
|
||||
.equals(config.getRateInterval(), rateIntervalUnit.toMillis(rateInterval));
|
||||
.equals(config.getRateInterval(), rateInterval.toMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public enum MaskType implements IMaskStrategy {
|
||||
CUSTOM {
|
||||
@Override
|
||||
public String mask(String str, char character, int left, int right) {
|
||||
return CharSequenceUtil.replace(str, left, str.length() - right, character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, left, str.length() - right, character);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ public enum MaskType implements IMaskStrategy {
|
||||
MOBILE_PHONE {
|
||||
@Override
|
||||
public String mask(String str, char character, int left, int right) {
|
||||
return CharSequenceUtil.replace(str, 3, str.length() - 4, character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, 3, str.length() - 4, character);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ public enum MaskType implements IMaskStrategy {
|
||||
FIXED_PHONE {
|
||||
@Override
|
||||
public String mask(String str, char character, int left, int right) {
|
||||
return CharSequenceUtil.replace(str, 4, str.length() - 2, character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, 4, str.length() - 2, character);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -76,7 +76,7 @@ public enum MaskType implements IMaskStrategy {
|
||||
if (index <= 1) {
|
||||
return str;
|
||||
}
|
||||
return CharSequenceUtil.replace(str, 1, index, character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, 1, index, character);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -89,7 +89,7 @@ public enum MaskType implements IMaskStrategy {
|
||||
ID_CARD {
|
||||
@Override
|
||||
public String mask(String str, char character, int left, int right) {
|
||||
return CharSequenceUtil.replace(str, 1, str.length() - 2, character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, 1, str.length() - 2, character);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -140,11 +140,11 @@ public enum MaskType implements IMaskStrategy {
|
||||
// 普通车牌
|
||||
int length = str.length();
|
||||
if (length == 7) {
|
||||
return CharSequenceUtil.replace(str, 3, 6, character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, 3, 6, character);
|
||||
}
|
||||
// 新能源车牌
|
||||
if (length == 8) {
|
||||
return CharSequenceUtil.replace(str, 3, 7, character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, 3, 7, character);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
@@ -159,7 +159,7 @@ public enum MaskType implements IMaskStrategy {
|
||||
CHINESE_NAME {
|
||||
@Override
|
||||
public String mask(String str, char character, int left, int right) {
|
||||
return CharSequenceUtil.replace(str, 1, str.length(), character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, 1, str.length(), character);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -186,7 +186,7 @@ public enum MaskType implements IMaskStrategy {
|
||||
@Override
|
||||
public String mask(String str, char character, int left, int right) {
|
||||
int length = str.length();
|
||||
return CharSequenceUtil.replace(str, length - 8, length, character);
|
||||
return CharSequenceUtil.replaceByCodePoint(str, length - 8, length, character);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@ package top.continew.starter.web.autoconfigure.response;
|
||||
|
||||
import com.feiniaojin.gracefulresponse.advice.lifecycle.exception.BeforeControllerAdviceProcess;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.web.util.SpringWebUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* 默认回调处理器实现
|
||||
@@ -38,10 +39,9 @@ public class DefaultBeforeControllerAdviceProcessImpl implements BeforeControlle
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(Throwable throwable) {
|
||||
public void call(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception e) {
|
||||
if (globalResponseProperties.isPrintExceptionInGlobalAdvice()) {
|
||||
HttpServletRequest request = SpringWebUtils.getRequest();
|
||||
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), throwable);
|
||||
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.feiniaojin.gracefulresponse.ExceptionAliasRegister;
|
||||
import com.feiniaojin.gracefulresponse.advice.*;
|
||||
import com.feiniaojin.gracefulresponse.advice.lifecycle.exception.BeforeControllerAdviceProcess;
|
||||
import com.feiniaojin.gracefulresponse.advice.lifecycle.exception.ControllerAdvicePredicate;
|
||||
import com.feiniaojin.gracefulresponse.advice.lifecycle.response.ResponseBodyAdvicePredicate;
|
||||
import com.feiniaojin.gracefulresponse.advice.lifecycle.exception.RejectStrategy;
|
||||
import com.feiniaojin.gracefulresponse.api.ResponseFactory;
|
||||
import com.feiniaojin.gracefulresponse.api.ResponseStatusFactory;
|
||||
import com.feiniaojin.gracefulresponse.defaults.DefaultResponseFactory;
|
||||
@@ -36,8 +36,10 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
|
||||
@@ -68,12 +70,7 @@ public class GlobalResponseAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrNotVoidResponseBodyAdvice grNotVoidResponseBodyAdvice() {
|
||||
GrNotVoidResponseBodyAdvice notVoidResponseBodyAdvice = new GrNotVoidResponseBodyAdvice();
|
||||
CopyOnWriteArrayList<ResponseBodyAdvicePredicate> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
|
||||
copyOnWriteArrayList.add(notVoidResponseBodyAdvice);
|
||||
notVoidResponseBodyAdvice.setPredicates(copyOnWriteArrayList);
|
||||
notVoidResponseBodyAdvice.setResponseBodyAdviceProcessor(notVoidResponseBodyAdvice);
|
||||
return notVoidResponseBodyAdvice;
|
||||
return new GrNotVoidResponseBodyAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,12 +79,7 @@ public class GlobalResponseAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrVoidResponseBodyAdvice grVoidResponseBodyAdvice() {
|
||||
GrVoidResponseBodyAdvice voidResponseBodyAdvice = new GrVoidResponseBodyAdvice();
|
||||
CopyOnWriteArrayList<ResponseBodyAdvicePredicate> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
|
||||
copyOnWriteArrayList.add(voidResponseBodyAdvice);
|
||||
voidResponseBodyAdvice.setPredicates(copyOnWriteArrayList);
|
||||
voidResponseBodyAdvice.setResponseBodyAdviceProcessor(voidResponseBodyAdvice);
|
||||
return voidResponseBodyAdvice;
|
||||
return new GrVoidResponseBodyAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,9 +95,10 @@ public class GlobalResponseAutoConfiguration {
|
||||
* 框架异常处理器
|
||||
*/
|
||||
@Bean
|
||||
public FrameworkExceptionAdvice frameworkExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess) {
|
||||
public FrameworkExceptionAdvice frameworkExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess,
|
||||
@Lazy RejectStrategy rejectStrategy) {
|
||||
FrameworkExceptionAdvice frameworkExceptionAdvice = new FrameworkExceptionAdvice();
|
||||
frameworkExceptionAdvice.setRejectStrategy(new DefaultRejectStrategyImpl());
|
||||
frameworkExceptionAdvice.setRejectStrategy(rejectStrategy);
|
||||
frameworkExceptionAdvice.setControllerAdviceProcessor(frameworkExceptionAdvice);
|
||||
frameworkExceptionAdvice.setBeforeControllerAdviceProcess(beforeControllerAdviceProcess);
|
||||
frameworkExceptionAdvice.setControllerAdviceHttpProcessor(frameworkExceptionAdvice);
|
||||
@@ -116,9 +109,10 @@ public class GlobalResponseAutoConfiguration {
|
||||
* 数据校验异常处理器
|
||||
*/
|
||||
@Bean
|
||||
public DataExceptionAdvice dataExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess) {
|
||||
public DataExceptionAdvice dataExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess,
|
||||
@Lazy RejectStrategy rejectStrategy) {
|
||||
DataExceptionAdvice dataExceptionAdvice = new DataExceptionAdvice();
|
||||
dataExceptionAdvice.setRejectStrategy(new DefaultRejectStrategyImpl());
|
||||
dataExceptionAdvice.setRejectStrategy(rejectStrategy);
|
||||
dataExceptionAdvice.setControllerAdviceProcessor(dataExceptionAdvice);
|
||||
dataExceptionAdvice.setBeforeControllerAdviceProcess(beforeControllerAdviceProcess);
|
||||
dataExceptionAdvice.setControllerAdviceHttpProcessor(dataExceptionAdvice);
|
||||
@@ -129,9 +123,10 @@ public class GlobalResponseAutoConfiguration {
|
||||
* 默认全局异常处理器
|
||||
*/
|
||||
@Bean
|
||||
public DefaultGlobalExceptionAdvice defaultGlobalExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess) {
|
||||
public DefaultGlobalExceptionAdvice defaultGlobalExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess,
|
||||
@Lazy RejectStrategy rejectStrategy) {
|
||||
DefaultGlobalExceptionAdvice advice = new DefaultGlobalExceptionAdvice();
|
||||
advice.setRejectStrategy(new DefaultRejectStrategyImpl());
|
||||
advice.setRejectStrategy(rejectStrategy);
|
||||
CopyOnWriteArrayList<ControllerAdvicePredicate> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
|
||||
copyOnWriteArrayList.add(advice);
|
||||
advice.setPredicates(copyOnWriteArrayList);
|
||||
@@ -145,27 +140,40 @@ public class GlobalResponseAutoConfiguration {
|
||||
* 默认参数校验异常处理器
|
||||
*/
|
||||
@Bean
|
||||
public DefaultValidationExceptionAdvice defaultValidationExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess) {
|
||||
public DefaultValidationExceptionAdvice defaultValidationExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess,
|
||||
@Lazy RejectStrategy rejectStrategy) {
|
||||
DefaultValidationExceptionAdvice advice = new DefaultValidationExceptionAdvice();
|
||||
advice.setRejectStrategy(new DefaultRejectStrategyImpl());
|
||||
advice.setRejectStrategy(rejectStrategy);
|
||||
advice.setControllerAdviceProcessor(advice);
|
||||
advice.setBeforeControllerAdviceProcess(beforeControllerAdviceProcess);
|
||||
// 设置默认参数校验异常http处理器
|
||||
advice.setControllerAdviceHttpProcessor(advice);
|
||||
return advice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝策略
|
||||
*/
|
||||
@Bean
|
||||
public RejectStrategy rejectStrategy() {
|
||||
return new DefaultRejectStrategyImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放异常处理器
|
||||
*/
|
||||
@Bean
|
||||
public ExceptionHandlerExceptionResolver releaseExceptionHandlerExceptionResolver() {
|
||||
return new ReleaseExceptionHandlerExceptionResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化支持
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
|
||||
public GrI18nResponseBodyAdvice grI18nResponseBodyAdvice() {
|
||||
GrI18nResponseBodyAdvice i18nResponseBodyAdvice = new GrI18nResponseBodyAdvice();
|
||||
CopyOnWriteArrayList<ResponseBodyAdvicePredicate> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
|
||||
copyOnWriteArrayList.add(i18nResponseBodyAdvice);
|
||||
i18nResponseBodyAdvice.setPredicates(copyOnWriteArrayList);
|
||||
i18nResponseBodyAdvice.setResponseBodyAdviceProcessor(i18nResponseBodyAdvice);
|
||||
return i18nResponseBodyAdvice;
|
||||
return new GrI18nResponseBodyAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package top.continew.starter.web.autoconfigure.xss;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
@@ -55,14 +55,14 @@ public class XssFilter implements Filter {
|
||||
if (servletRequest instanceof HttpServletRequest request && xssProperties.isEnabled()) {
|
||||
// 放行路由:忽略 XSS 过滤
|
||||
List<String> excludePatterns = xssProperties.getExcludePatterns();
|
||||
if (CollectionUtil.isNotEmpty(excludePatterns) && SpringWebUtils.isMatch(request
|
||||
if (CollUtil.isNotEmpty(excludePatterns) && SpringWebUtils.isMatch(request
|
||||
.getServletPath(), excludePatterns)) {
|
||||
filterChain.doFilter(request, servletResponse);
|
||||
return;
|
||||
}
|
||||
// 拦截路由:执行 XSS 过滤
|
||||
List<String> includePatterns = xssProperties.getIncludePatterns();
|
||||
if (CollectionUtil.isNotEmpty(includePatterns)) {
|
||||
if (CollUtil.isNotEmpty(includePatterns)) {
|
||||
if (SpringWebUtils.isMatch(request.getServletPath(), includePatterns)) {
|
||||
filterChain.doFilter(new XssServletRequestWrapper(request, xssProperties), servletResponse);
|
||||
} else {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package top.continew.starter.web.autoconfigure.xss;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
@@ -110,7 +110,7 @@ public class XssServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
// 转义
|
||||
if (XssMode.ESCAPE.equals(mode)) {
|
||||
List<String> reStr = ReUtil.findAllGroup0(HtmlUtil.RE_HTML_MARK, content);
|
||||
if (CollectionUtil.isEmpty(reStr)) {
|
||||
if (CollUtil.isEmpty(reStr)) {
|
||||
return content;
|
||||
}
|
||||
for (String s : reStr) {
|
||||
|
||||
Reference in New Issue
Block a user