mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-14 12:57:09 +08:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c002e6c593 | |||
| 0b7eea6dac | |||
| 52dce2acdf | |||
| 7b12454ce6 | |||
| 76e282c796 | |||
| 7023be1072 | |||
| 2dd697a115 | |||
|
|
edc47b7b1d | ||
| 48f894b8b6 | |||
| 3788da72c2 | |||
| 1df1f6de6b | |||
|
|
d790e6941f | ||
| 4dae89e0f2 | |||
| be7972c00b | |||
| 556bfb924a | |||
| 70ae383de6 | |||
| 7c6df66ca5 | |||
| dec286d9de | |||
|
|
bcb13326c6 | ||
| 5500e5d840 | |||
| e9f01d05c9 | |||
| 0a0d022586 | |||
| 72f55697cc | |||
| c4007fa290 | |||
| dac525f177 | |||
| 22fee2f5bd | |||
| 621a5e3b22 | |||
| 2a70a9a252 | |||
| e0e5944b45 | |||
| 9cf3ae87a1 | |||
| cd6826a0ab | |||
| c4459d1b8d | |||
| 65f5fbd6da | |||
| 3e9a59df5a | |||
| ad1d001973 | |||
| ac70c385f7 | |||
| 1adfddfa3b | |||
| bc00c9bab0 | |||
| 7997267060 | |||
| 083bc7b38a | |||
| af351e04b9 | |||
| fd14e44648 | |||
| b9fbd3830d |
BIN
.image/模块依赖图.png
BIN
.image/模块依赖图.png
Binary file not shown.
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 170 KiB |
53
CHANGELOG.md
53
CHANGELOG.md
@@ -1,3 +1,56 @@
|
||||
## [v1.1.0](https://github.com/Charles7c/continew-starter/compare/v1.0.1...v1.1.0) (2023-12-31)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
* 【log/httptrace-pro】新增 continew-starter-log-httptrace-pro 日志模块(Spring Boot Actuator HttpTrace 重置增强版)
|
||||
* 【storage/local】新增 continew-starter-storage-local 本地存储模块 ([7e5ff2e](https://github.com/Charles7c/continew-starter/commit/cd6826a0abe0666f9fe867e92bf70abb47e5ff2e))
|
||||
* 【cache/redisson】RedisUtils 新增限流方法 ([fc5c20c](https://github.com/Charles7c/continew-starter/commit/9cf3ae87a1a20db9ee8b2b7272e8328b5fc5c20c))
|
||||
* 【data/mybatis-plus】新增数据权限默认解决方案 ([31e09af](https://github.com/Charles7c/continew-starter/commit/621a5e3b22db9b81d31c65b39ad387a8531e09af))
|
||||
* 【captcha/behavior】新增 continew-starter-captcha-behavior 行为验证码模块 ([Gitee PR#1](https://gitee.com/Charles7c/continew-starter/pulls/1))
|
||||
* 【core】新增 PATH_PATTERN 字符串常量 ([40d0f7f](https://github.com/Charles7c/continew-starter/commit/76e282c7965fdfa39854fe77397687bbc40d0f7f))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【core】优化跨域配置默认值 ([23233ce](https://github.com/Charles7c/continew-starter/commit/65f5fbd6daa9ae2c8aedd13c487e8985523233ce))
|
||||
- 【extension/crud】新增全局异常处理器 ([a285b55](https://github.com/Charles7c/continew-starter/commit/c4459d1b8d701a4405f74ea92cfc87752a285b55))
|
||||
- 【extension/crud】移除部分方法中仅有单个非读操作的事务处理 ([54f83f5](https://github.com/Charles7c/continew-starter/commit/70ae383de62bc3c6ae0d2e1c3cf5c005d54f83f5))
|
||||
|
||||
### 📦 依赖升级
|
||||
|
||||
- 【dependencies】Spring Boot 3.1.5 => 3.1.7 ([8636605](https://github.com/Charles7c/continew-starter/commit/72f55697cc8958bf3586daed03a8d1b3c8636605))
|
||||
- 【dependencies】Just Auth 1.16.5 => 1.16.6 ([8636605](https://github.com/Charles7c/continew-starter/commit/72f55697cc8958bf3586daed03a8d1b3c8636605))
|
||||
- 【dependencies】Redisson 3.24.3 => 3.25.2 ([8636605](https://github.com/Charles7c/continew-starter/commit/72f55697cc8958bf3586daed03a8d1b3c8636605))
|
||||
- 【dependencies】Easy Excel 3.3.2 => 3.3.3 ([8636605](https://github.com/Charles7c/continew-starter/commit/72f55697cc8958bf3586daed03a8d1b3c8636605))
|
||||
- 【dependencies】Knife4j 4.3.0 => 4.4.0 ([8636605](https://github.com/Charles7c/continew-starter/commit/72f55697cc8958bf3586daed03a8d1b3c8636605))
|
||||
- 【dependencies】Hutool 5.8.23 => 5.8.24 ([8636605](https://github.com/Charles7c/continew-starter/commit/72f55697cc8958bf3586daed03a8d1b3c8636605))
|
||||
- 【dependencies】MyBatis Plus 3.5.4.1 => 3.5.5(修复与 Spring Boot 3.1.7 的 DdlApplicationRunner冲突错误) ([b36d578](https://github.com/Charles7c/continew-starter/commit/556bfb924a1e5834fe0a101b9ff52cc5bb36d578))
|
||||
- 【dependencies】新增 X File Storage 依赖版本 2.0.0 ([016de2d](https://github.com/Charles7c/continew-starter/commit/be7972c00be8d62cc25332e053a985532016de2d))
|
||||
- 【dependencies】ip2region 3.1.5.1 => 3.1.6 ([980c929](https://github.com/Charles7c/continew-starter/commit/4dae89e0f21ac6c532101e983ee4007f3980c929))
|
||||
- 【dependencies】新增 Amazon S3 依赖版本 1.12.626 ([97e4a2d](https://github.com/Charles7c/continew-starter/commit/48f894b8b62f8b968091dcea51b57336b97e4a2d))
|
||||
|
||||
### 💥 破坏性变更
|
||||
|
||||
- 【captcha/graphic】优化图形验证码配置前缀 ([7b3e450](https://github.com/Charles7c/continew-starter/commit/e0e5944b45bcbf8a4b7a5066ad347459a7b3e450))
|
||||
- 【data/mybatis-plus】调整 IBaseEnum 所属包 enums => base ([522833c](https://github.com/Charles7c/continew-starter/commit/22fee2f5bd8211e26c2f6a163a6298f5b522833c))
|
||||
- 【auth/satoken】SaTokenDaoTypeEnum => SaTokenDaoType ([86e35c4](https://github.com/Charles7c/continew-starter/commit/0a0d022586dc88a773512c5761c68d62786e35c4))
|
||||
- 【core】使用常量优化部分魔法值,核心模块部分配置前缀调整 ([999f899](https://github.com/Charles7c/continew-starter/commit/52dce2acdfa0296c3f6f4875f14a0299f999f899))
|
||||
|
||||
## [v1.0.1](https://github.com/Charles7c/continew-starter/compare/v1.0.0...v1.0.1) (2023-12-13)
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【data/mybatis-plus】QueryTypeEnum => QueryType,并取消实现 IBaseEnum 接口 ([bc00c9b](https://github.com/Charles7c/continew-starter/commit/bc00c9bab0ed4508fd1dc0da8a76ef96739cce1d))
|
||||
- 【api-doc】新增鉴权配置 ([7997267](https://github.com/Charles7c/continew-starter/commit/7997267060b3e79f80dd73cec722bc295635a93b))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【extension/crud】修复使用 @CrudRequestMapping 后自定义 API 不显示的问题 ([1adfddf](https://github.com/Charles7c/continew-starter/commit/1adfddfa3b276e764b098512b2e9c75f007d13c1))
|
||||
|
||||
### 💥 破坏性变更
|
||||
|
||||
- 【extension/crud】调整通用查询注解所属模块 crud => mybatis-plus ([083bc7b](https://github.com/Charles7c/continew-starter/commit/083bc7b38a861339ceb7a06acdd20ea64bc84990))
|
||||
- 【extension/crud】调整校验工具类所属模块 crud => core ([083bc7b](https://github.com/Charles7c/continew-starter/commit/083bc7b38a861339ceb7a06acdd20ea64bc84990))
|
||||
|
||||
## v1.0.0 (2023-12-02)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
110
README.md
110
README.md
@@ -4,7 +4,7 @@
|
||||
<img src="https://img.shields.io/badge/License-LGPL--3.0-blue.svg" alt="License" />
|
||||
</a>
|
||||
<a href="https://github.com/Charles7c/continew-starter" target="_blank">
|
||||
<img src="https://img.shields.io/badge/RELEASE-v1.0.0-%23ff3f59.svg" alt="Release" />
|
||||
<img src="https://img.shields.io/badge/RELEASE-v1.1.0-%23ff3f59.svg" alt="Release" />
|
||||
</a>
|
||||
<a href="https://github.com/Charles7c/continew-starter" target="_blank">
|
||||
<img src="https://img.shields.io/github/stars/Charles7c/continew-starter?style=social" alt="GitHub stars" />
|
||||
@@ -48,8 +48,8 @@ ContiNew Starter 就是将脚手架项目中的通用基础配置进行了封装
|
||||
|
||||
## 项目源码
|
||||
|
||||
| 开源平台 | 源码地址 |
|
||||
| ------------- | ------------------------------------------- |
|
||||
| 开源平台 | 源码地址 |
|
||||
| :------------ | :-------------------------------------------- |
|
||||
| GitHub | https://github.com/Charles7c/continew-starter |
|
||||
| Gitee(码云) | https://gitee.com/Charles7c/continew-starter |
|
||||
|
||||
@@ -63,7 +63,7 @@ ContiNew Starter 就是将脚手架项目中的通用基础配置进行了封装
|
||||
<parent>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>{latest-version}</version>
|
||||
</parent>
|
||||
```
|
||||
|
||||
@@ -83,7 +83,7 @@ ContiNew Starter 就是将脚手架项目中的通用基础配置进行了封装
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-dependencies</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>{latest-version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -121,22 +121,50 @@ cors:
|
||||
exposed-headers: '*'
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>抢先体验快照(SNAPSHOT)版本💡</summary>
|
||||
|
||||
> **注意:** 快照版本目前处于开发测试阶段,其中很多特性或改动尚不稳定,可能会因为修复或优化而频繁调整。因此,仅可用于体验,切勿用于生产环境!
|
||||
|
||||
1.在项目 pom.xml 中配置 SNAPSHOT(快照)仓库地址(如果你已配有其他仓库地址,追加下方快照仓库地址即可)
|
||||
|
||||
```xml
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>sonatype-nexus-snapshots</id>
|
||||
<name>Sonatype Nexus Snapshots</name>
|
||||
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
|
||||
<snapshots>
|
||||
<updatePolicy>always</updatePolicy>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
```
|
||||
|
||||
2.将 ContiNew Starter 版本改为对应快照版本,例如:1.1.0-SNAPSHOT
|
||||
|
||||
</details>
|
||||
|
||||
## 模块结构
|
||||
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
| ---------------------------------- | ---------------------------------------- | ------------------------------------------------------------ |
|
||||
| continew-starter-core | 核心模块:包含跨域、线程池等自动配置 | <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a>:3.1.5<br /><a href="https://undertow.io/" target="_blank">Undertow</a>:2.3.10.Final<br /><a href="https://www.hutool.cn/" target="_blank">Hutool</a>:5.8.23<br />mica-ip2region:3.1.5.1 |
|
||||
| continew-starter-file-excel | 文件处理模块:Excel 相关配置 | <a href="https://easyexcel.opensource.alibaba.com/" target="_blank">Easy Excel</a>:3.3.2 |
|
||||
| continew-starter-json-jackson | JSON 模块:Jackson 自动配置 | Jackson:2.15.3 |
|
||||
| continew-starter-api-doc | API 文档模块:Knife4j 自动配置 | <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a>:4.3.0 |
|
||||
| continew-starter-captcha-graphic | 验证码模块:图形验证码 | Easy Captcha:1.6.2 |
|
||||
| continew-starter-cache-redisson | 缓存模块:Redisson 自动配置 | <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a>:3.24.3 |
|
||||
| continew-starter-data-mybatis-plus | 数据访问模块:MyBatis Plus 自动配置 | <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a>:3.5.4.1<br /><a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a>:4.2.0<br /><a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a>:3.9.1 |
|
||||
| continew-starter-auth-satoken | 认证模块:SaToken 自动配置 | <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token</a>:1.37.0 |
|
||||
| continew-starter-auth-justauth | 认证模块:JustAuth 自动配置 | <a href="https://justauth.cn/" target="_blank">Just Auth</a>:1.16.5 |
|
||||
| continew-starter-messaging-mail | 消息模块:邮件 | Jakarta Mail:1.1.0 |
|
||||
| continew-starter-messaging-sms | 消息模块:短信 | <a href="https://sms4j.com/" target="_blank">SMS4J</a>:3.0.4 |
|
||||
| continew-starter-extension | 扩展模块:包含 CRUD 等复杂模组及自动配置 | |
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
| ---------------------------------- | --------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| continew-starter-core | 核心模块:包含跨域、线程池等自动配置 | <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a>:3.1.7<br /><a href="https://undertow.io/" target="_blank">Undertow</a>:2.3.10.Final<br /><a href="https://www.hutool.cn/" target="_blank">Hutool</a>:5.8.24<br />mica-ip2region:3.1.6 |
|
||||
| continew-starter-json-jackson | JSON 模块:Jackson 自动配置 | Jackson:2.15.3 |
|
||||
| continew-starter-api-doc | API 文档模块:Knife4j 自动配置 | <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a>:4.4.0 |
|
||||
| continew-starter-log-httptrace-pro | 日志模块:Spring Boot Actuator HttpTrace 重置增强版 | |
|
||||
| continew-starter-storage-local | 存储模块:本地存储 | |
|
||||
| continew-starter-file-excel | 文件处理模块:Excel 相关配置 | <a href="https://easyexcel.opensource.alibaba.com/" target="_blank">Easy Excel</a>:3.3.4 |
|
||||
| continew-starter-captcha-graphic | 验证码模块:图形验证码 | Easy Captcha:1.6.2 |
|
||||
| continew-starter-captcha-behavior | 验证码模块:行为验证码 | AJ-Captcha:1.3.0 |
|
||||
| continew-starter-cache-redisson | 缓存模块:Redisson 自动配置 | <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a>:3.25.2 |
|
||||
| continew-starter-data-mybatis-plus | 数据访问模块:MyBatis Plus 自动配置 | <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a>:3.5.5<br /><a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a>:4.2.0<br /><a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a>:3.9.1 |
|
||||
| continew-starter-auth-satoken | 认证模块:SaToken 自动配置 | <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token</a>:1.37.0 |
|
||||
| continew-starter-auth-justauth | 认证模块:JustAuth 自动配置 | <a href="https://justauth.cn/" target="_blank">Just Auth</a>:1.16.6 |
|
||||
| continew-starter-messaging-mail | 消息模块:邮件 | Jakarta Mail:1.1.0 |
|
||||
| continew-starter-messaging-sms | 消息模块:短信 | <a href="https://sms4j.com/" target="_blank">SMS4J</a>:3.0.4 |
|
||||
| continew-starter-extension-crud | 扩展模块:CRUD 通用内容封装 | |
|
||||
|
||||

|
||||
|
||||
@@ -171,30 +199,6 @@ ContiNew Starter 的分支目前分为下个大版本的开发分支和上个大
|
||||
> 2. 在提交代码前,请按照 [Angular 提交规范](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular) 编写 commit 的 message(建议在 IntelliJ IDEA 中下载并安装 Git Commit Template 插件,以便按照规范进行 commit)
|
||||
> 3. 提交代码之前,请关闭所有代码窗口,执行 mvn compile 命令,编译通过后,不要再打开查看任何代码窗口,直接提交即可
|
||||
|
||||
## 谁在使用?
|
||||
|
||||
- ✨[ContiNew Admin](https://github.com/Charles7c/continew-admin):ContiNew Admin (Continue New Admin)中后台管理框架/脚手架,持续以最新流行技术栈构建,拥抱变化,迭代优化。依托开源协作模式,提升技术透明度、放大集体智慧、共创优秀实践,源源不断地为企业级项目开发提供助力。当前采用的技术栈:Spring Boot3(Java17)、Vue3 & Arco Design、Sa-Token、MyBatis Plus、Redisson、Liquibase、JustAuth、Easy Excel、Hutool、TypeScript、Vite4 等。
|
||||
|
||||
## 鸣谢
|
||||
|
||||
### 鸣谢
|
||||
|
||||
感谢参与贡献的每一位小伙伴🥰
|
||||
|
||||
<a href="https://github.com/Charles7c/continew-starter/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Charles7c/continew-starter" />
|
||||
</a>
|
||||
|
||||
### 特别鸣谢
|
||||
|
||||
- 感谢 <a href="https://www.jetbrains.com/" target="_blank">JetBrains</a> 提供的 <a href="https://www.jetbrains.com/shop/eform/opensource" target="_blank">非商业开源软件开发授权</a>
|
||||
- 感谢 <a href="https://github.com/baomidou/mybatis-plus" target="_blank">MyBatis Plus</a>、<a href="https://github.com/dromara/sa-token" target="_blank">Sa-Token</a> 、<a href="https://github.com/xiaoymin/knife4j" target="_blank">Knife4j</a>、<a href="https://github.com/dromara/hutool" target="_blank">Hutool</a> 等国产开源组件作者为国内开源世界作出的贡献
|
||||
- 感谢 <a href="https://github.com/elunez/eladmin" target="_blank">ELADMIN</a>、<a href="https://github.com/dromara/RuoYi-Vue-Plus" target="_blank">RuoYi-Vue-Plus</a>、<a href="https://gitee.com/herodotus/dante-engine" target="_blank">Dante-Engine</a>,致敬各位作者为开源脚手架领域作出的贡献
|
||||
- e.g. 脱胎于 ELADMIN 项目开源的 QueryHelper 组件
|
||||
- e.g. 使用 RuoYi-Vue-Plus 项目封装的 SaToken 相关认证鉴权配置
|
||||
- e.g. 使用 Dante-Engine 项目封装的 Redisson 相关配置
|
||||
- 感谢项目使用或未使用到的每一款开源组件,致敬各位开源先驱 :fire:
|
||||
|
||||
## 反馈交流
|
||||
|
||||
💬 欢迎各位小伙伴儿扫描下方二维码加好友,备注 `cnadmin`,拉你进群,探讨技术、提提需求~
|
||||
@@ -214,6 +218,26 @@ ContiNew Starter 的分支目前分为下个大版本的开发分支和上个大
|
||||
💬 如无加群意愿,欢迎在 <a href="https://github.com/Charles7c/continew-starter/issues" target="_blank">Issues</a> 中反馈交流~ 🍻
|
||||
</details>
|
||||
|
||||
## 鸣谢
|
||||
|
||||
### 鸣谢
|
||||
|
||||
感谢参与贡献的每一位小伙伴🥰
|
||||
|
||||
<a href="https://github.com/Charles7c/continew-starter/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Charles7c/continew-starter" />
|
||||
</a>
|
||||
|
||||
### 特别鸣谢
|
||||
|
||||
- 感谢 <a href="https://www.jetbrains.com/" target="_blank">JetBrains</a> 提供的 <a href="https://www.jetbrains.com/shop/eform/opensource" target="_blank">非商业开源软件开发授权</a>
|
||||
- 感谢 <a href="https://github.com/baomidou/mybatis-plus" target="_blank">MyBatis Plus</a>、<a href="https://github.com/dromara/sa-token" target="_blank">Sa-Token</a> 、<a href="https://github.com/xiaoymin/knife4j" target="_blank">Knife4j</a>、<a href="https://github.com/dromara/hutool" target="_blank">Hutool</a> 等国产开源组件作者为国内开源世界作出的贡献
|
||||
- 感谢 <a href="https://github.com/elunez/eladmin" target="_blank">ELADMIN</a>、<a href="https://github.com/dromara/RuoYi-Vue-Plus" target="_blank">RuoYi-Vue-Plus</a>、<a href="https://gitee.com/herodotus/dante-engine" target="_blank">Dante-Engine</a>,致敬各位作者为开源脚手架领域作出的贡献
|
||||
- e.g. 脱胎于 ELADMIN 项目开源的 QueryHelper 组件
|
||||
- e.g. 扩展于 RuoYi-Vue-Plus 项目封装的 SaToken 相关认证鉴权配置
|
||||
- e.g. 扩展于 Dante-Engine 项目封装的 Redisson 相关配置
|
||||
- 感谢项目使用或未使用到的每一款开源组件,致敬各位开源先驱 :fire:
|
||||
|
||||
## License
|
||||
|
||||
- 遵循 <a href="https://github.com/Charles7c/continew-starter/blob/dev/LICENSE" target="_blank">LGPL-3.0</a> 开源许可协议
|
||||
|
||||
@@ -16,15 +16,21 @@
|
||||
|
||||
package top.charles7c.continew.starter.apidoc.autoconfigure;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.http.CacheControl;
|
||||
@@ -34,6 +40,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.charles7c.continew.starter.core.autoconfigure.project.ProjectProperties;
|
||||
import top.charles7c.continew.starter.core.handler.GeneralPropertySourceFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
@@ -47,6 +55,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@EnableWebMvc
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(name = "springdoc.swagger-ui.enabled", havingValue = "true")
|
||||
@EnableConfigurationProperties(SpringDocExtensionProperties.class)
|
||||
@PropertySource(value = "classpath:default-api-doc.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@@ -63,23 +72,62 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OpenAPI openApi(ProjectProperties properties) {
|
||||
public OpenAPI openApi(ProjectProperties projectProperties, SpringDocExtensionProperties properties) {
|
||||
Info info = new Info()
|
||||
.title(String.format("%s %s", properties.getName(), "API 文档"))
|
||||
.version(properties.getVersion())
|
||||
.description(properties.getDescription());
|
||||
ProjectProperties.Contact contact = properties.getContact();
|
||||
.title(String.format("%s %s", projectProperties.getName(), "API 文档"))
|
||||
.version(projectProperties.getVersion())
|
||||
.description(projectProperties.getDescription());
|
||||
ProjectProperties.Contact contact = projectProperties.getContact();
|
||||
if (null != contact) {
|
||||
info.contact(new Contact().name(contact.getName())
|
||||
.email(contact.getEmail())
|
||||
.url(contact.getUrl()));
|
||||
}
|
||||
ProjectProperties.License license = properties.getLicense();
|
||||
ProjectProperties.License license = projectProperties.getLicense();
|
||||
if (null != license) {
|
||||
info.license(new License().name(license.getName())
|
||||
.url(license.getUrl()));
|
||||
}
|
||||
return new OpenAPI().info(info);
|
||||
OpenAPI openAPI = new OpenAPI();
|
||||
openAPI.info(info);
|
||||
Components components = properties.getComponents();
|
||||
if (null != components) {
|
||||
openAPI.components(components);
|
||||
// 鉴权配置
|
||||
Map<String, SecurityScheme> securitySchemeMap = components.getSecuritySchemes();
|
||||
if (MapUtil.isNotEmpty(securitySchemeMap)) {
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||
List<String> list = securitySchemeMap.values().stream().map(SecurityScheme::getName).toList();
|
||||
list.forEach(securityRequirement::addList);
|
||||
openAPI.addSecurityItem(securityRequirement);
|
||||
}
|
||||
}
|
||||
return openAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局自定义配置(全局添加鉴权参数)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GlobalOpenApiCustomizer globalOpenApiCustomizer(SpringDocExtensionProperties properties) {
|
||||
return openApi -> {
|
||||
if (null != openApi.getPaths()) {
|
||||
openApi.getPaths().forEach((s, pathItem) -> {
|
||||
// 为所有接口添加鉴权
|
||||
Components components = properties.getComponents();
|
||||
if (null != components && MapUtil.isNotEmpty(components.getSecuritySchemes())) {
|
||||
Map<String, SecurityScheme> securitySchemeMap = components.getSecuritySchemes();
|
||||
pathItem.readOperations().forEach(operation -> {
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||
List<String> list = securitySchemeMap.values().stream().map(SecurityScheme::getName).toList();
|
||||
list.forEach(securityRequirement::addList);
|
||||
operation.addSecurityItem(securityRequirement);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.apidoc.autoconfigure;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
|
||||
/**
|
||||
* API 文档扩展配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "springdoc")
|
||||
public class SpringDocExtensionProperties {
|
||||
|
||||
/**
|
||||
* 组件配置(包括鉴权配置等)
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private Components components;
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
import top.charles7c.continew.starter.core.handler.GeneralPropertySourceFactory;
|
||||
|
||||
/**
|
||||
@@ -56,7 +57,7 @@ public class SaTokenAutoConfiguration implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验
|
||||
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin())).addPathPatterns("/**")
|
||||
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin())).addPathPatterns(StringConstants.PATH_PATTERN)
|
||||
.excludePathPatterns(properties.getSecurity().getExcludes());
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ package top.charles7c.continew.starter.auth.satoken.enums;
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public enum SaTokenDaoTypeEnum {
|
||||
public enum SaTokenDaoType {
|
||||
|
||||
/**
|
||||
* Redis
|
||||
@@ -18,7 +18,7 @@ package top.charles7c.continew.starter.auth.satoken.properties;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import lombok.Data;
|
||||
import top.charles7c.continew.starter.auth.satoken.enums.SaTokenDaoTypeEnum;
|
||||
import top.charles7c.continew.starter.auth.satoken.enums.SaTokenDaoType;
|
||||
|
||||
/**
|
||||
* SaToken 持久层配置属性
|
||||
@@ -32,7 +32,7 @@ public class SaTokenDaoProperties {
|
||||
/**
|
||||
* 持久层类型
|
||||
*/
|
||||
private SaTokenDaoTypeEnum type;
|
||||
private SaTokenDaoType type;
|
||||
|
||||
/**
|
||||
* 自定义持久层实现类(当 type 为 CUSTOM 时必填)
|
||||
|
||||
@@ -81,7 +81,7 @@ public class RedisUtils {
|
||||
* 删除缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @return true 设置成功;false 设置失败
|
||||
* @return true:设置成功;false:设置失败
|
||||
*/
|
||||
public static boolean delete(final String key) {
|
||||
return CLIENT.getBucket(key).delete();
|
||||
@@ -92,7 +92,7 @@ public class RedisUtils {
|
||||
*
|
||||
* @param key 键
|
||||
* @param timeout 过期时间(单位:秒)
|
||||
* @return true 设置成功;false 设置失败
|
||||
* @return true:设置成功;false:设置失败
|
||||
*/
|
||||
public static boolean expire(final String key, final long timeout) {
|
||||
return expire(key, Duration.ofSeconds(timeout));
|
||||
@@ -103,7 +103,7 @@ public class RedisUtils {
|
||||
*
|
||||
* @param key 键
|
||||
* @param duration 过期时间
|
||||
* @return true 设置成功;false 设置失败
|
||||
* @return true:设置成功;false:设置失败
|
||||
*/
|
||||
public static boolean expire(final String key, final Duration duration) {
|
||||
return CLIENT.getBucket(key).expire(duration);
|
||||
@@ -123,7 +123,7 @@ public class RedisUtils {
|
||||
* 是否存在指定缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @return true 存在;false 不存在
|
||||
* @return true:存在;false:不存在
|
||||
*/
|
||||
public static boolean hasKey(String key) {
|
||||
RKeys keys = CLIENT.getKeys();
|
||||
@@ -141,6 +141,21 @@ public class RedisUtils {
|
||||
return stream.map(key -> getNameMapper().unmap(key)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @param key 键
|
||||
* @param rateType 限流类型(OVERALL:全局限流;PER_CLIENT:单机限流)
|
||||
* @param rate 速率(指定时间间隔产生的令牌数)
|
||||
* @param rateInterval 速率间隔(时间间隔,单位:秒)
|
||||
* @return true:成功;false:失败
|
||||
*/
|
||||
public static boolean rateLimit(String key, RateType rateType, int rate, int rateInterval) {
|
||||
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
|
||||
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
|
||||
return rateLimiter.tryAcquire(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化键,将各子键用 : 拼接起来
|
||||
*
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-captcha</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-captcha-behavior</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 验证码模块 - 行为验证码</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- AJ-Captcha(行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式) -->
|
||||
<dependency>
|
||||
<groupId>com.anji-plus</groupId>
|
||||
<artifactId>captcha</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.captcha.behavior.autoconfigure;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.anji.captcha.model.common.Const;
|
||||
import com.anji.captcha.service.CaptchaService;
|
||||
import com.anji.captcha.service.impl.CaptchaServiceFactory;
|
||||
import com.anji.captcha.util.ImageUtils;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 行为验证码自动配置
|
||||
*
|
||||
* @author Bull-BCLS
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@AutoConfiguration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "continew-starter.captcha.behavior", name = "enabled", havingValue = "true")
|
||||
@EnableConfigurationProperties(BehaviorCaptchaProperties.class)
|
||||
public class BehaviorCaptchaAutoConfiguration {
|
||||
|
||||
private final BehaviorCaptchaProperties properties;
|
||||
|
||||
/**
|
||||
* 自定义缓存实现配置
|
||||
*/
|
||||
@Configuration
|
||||
@Import({BehaviorCaptchaCacheConfiguration.Redis.class, BehaviorCaptchaCacheConfiguration.Custom.class})
|
||||
protected static class BehaviorCaptchaCacheAutoConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* 行为验证码服务接口
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CaptchaService captchaService() {
|
||||
Properties config = new Properties();
|
||||
config.put(Const.CAPTCHA_CACHETYPE, properties.getCacheType().name().toLowerCase());
|
||||
config.put(Const.CAPTCHA_WATER_MARK, properties.getWaterMark());
|
||||
config.put(Const.CAPTCHA_FONT_TYPE, properties.getFontType());
|
||||
config.put(Const.CAPTCHA_TYPE, properties.getType().getCodeValue());
|
||||
config.put(Const.CAPTCHA_INTERFERENCE_OPTIONS, properties.getInterferenceOptions());
|
||||
config.put(Const.ORIGINAL_PATH_JIGSAW, StrUtil.emptyIfNull(properties.getJigsawBaseMapPath()));
|
||||
config.put(Const.ORIGINAL_PATH_PIC_CLICK, StrUtil.emptyIfNull(properties.getPicClickBaseMapPath()));
|
||||
config.put(Const.CAPTCHA_SLIP_OFFSET, properties.getSlipOffset());
|
||||
config.put(Const.CAPTCHA_AES_STATUS, String.valueOf(properties.getEnableAes()));
|
||||
config.put(Const.CAPTCHA_WATER_FONT, properties.getWaterFont());
|
||||
config.put(Const.CAPTCHA_CACAHE_MAX_NUMBER, properties.getCacheNumber());
|
||||
config.put(Const.CAPTCHA_TIMING_CLEAR_SECOND, properties.getTimingClear());
|
||||
config.put(Const.HISTORY_DATA_CLEAR_ENABLE, properties.getHistoryDataClearEnable());
|
||||
config.put(Const.REQ_FREQUENCY_LIMIT_ENABLE, properties.getReqFrequencyLimitEnable());
|
||||
config.put(Const.REQ_GET_LOCK_LIMIT, properties.getReqGetLockLimit());
|
||||
config.put(Const.REQ_GET_LOCK_SECONDS, properties.getReqGetLockSeconds());
|
||||
config.put(Const.REQ_GET_MINUTE_LIMIT, properties.getReqGetMinuteLimit());
|
||||
config.put(Const.REQ_CHECK_MINUTE_LIMIT, properties.getReqCheckMinuteLimit());
|
||||
config.put(Const.REQ_VALIDATE_MINUTE_LIMIT, properties.getReqVerifyMinuteLimit());
|
||||
config.put(Const.CAPTCHA_FONT_SIZE, properties.getFontSize());
|
||||
config.put(Const.CAPTCHA_FONT_STYLE, properties.getFontStyle());
|
||||
config.put(Const.CAPTCHA_WORD_COUNT, 4);
|
||||
if (StrUtil.startWith(properties.getJigsawBaseMapPath(), "classpath:")
|
||||
|| StrUtil.startWith(properties.getPicClickBaseMapPath(), "classpath:")) {
|
||||
// 自定义 resources 目录下初始化底图
|
||||
config.put(Const.CAPTCHA_INIT_ORIGINAL, true);
|
||||
initializeBaseMap(properties.getJigsawBaseMapPath(), properties.getPicClickBaseMapPath());
|
||||
}
|
||||
return CaptchaServiceFactory.getInstance(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 行为/点选 验证码底图
|
||||
*
|
||||
* @param jigsaw 行为验证码底图路径
|
||||
* @param picClick 点选验证码底图路径
|
||||
*/
|
||||
private static void initializeBaseMap(String jigsaw, String picClick) {
|
||||
ImageUtils.cacheBootImage(getResourcesImagesFile(jigsaw + "/original/*.png"),
|
||||
getResourcesImagesFile(jigsaw + "/slidingBlock/*.png"),
|
||||
getResourcesImagesFile(picClick + "/*.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片
|
||||
*
|
||||
* @param path 图片路径
|
||||
* @return key:图片文件名称;value:图片
|
||||
*/
|
||||
private static Map<String, String> getResourcesImagesFile(String path) {
|
||||
Map<String, String> imgMap = new HashMap<>();
|
||||
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
try {
|
||||
Resource[] resources = resolver.getResources(path);
|
||||
for (Resource resource : resources) {
|
||||
String imageName = resource.getFilename();
|
||||
byte[] imageValue = FileUtil.readBytes(resource.getFile());
|
||||
imgMap.put(imageName, Base64.encode(imageValue));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("读取路径为 [{}] 下的图片文件失败", path, e);
|
||||
}
|
||||
return imgMap;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.info("[ContiNew Starter] - Auto Configuration 'Behavior Captcha' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.captcha.behavior.autoconfigure;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.anji.captcha.service.CaptchaCacheService;
|
||||
import com.anji.captcha.service.impl.CaptchaServiceFactory;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import top.charles7c.continew.starter.cache.redisson.autoconfigure.RedissonAutoConfiguration;
|
||||
import top.charles7c.continew.starter.captcha.behavior.enums.StorageType;
|
||||
import top.charles7c.continew.starter.captcha.behavior.impl.BehaviorCaptchaCacheServiceImpl;
|
||||
|
||||
/**
|
||||
* 行为验证码缓存配置
|
||||
*
|
||||
* @author Bull-BCLS
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
abstract class BehaviorCaptchaCacheConfiguration {
|
||||
|
||||
/**
|
||||
* 自定义缓存实现类-Redis
|
||||
*/
|
||||
@ConditionalOnClass(RedisClient.class)
|
||||
@AutoConfigureBefore(RedissonAutoConfiguration.class)
|
||||
@ConditionalOnProperty(name = "continew-starter.captcha.behavior.cache-type", havingValue = "redis")
|
||||
static class Redis {
|
||||
static {
|
||||
CaptchaServiceFactory.cacheService.put(StorageType.REDIS.name().toLowerCase(), new BehaviorCaptchaCacheServiceImpl());
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Behavior-CaptchaCache-Redis' completed initialization.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义缓存实现类-自定义
|
||||
*/
|
||||
@ConditionalOnProperty(name = "continew-starter.captcha.behavior.cache-type", havingValue = "custom")
|
||||
static class Custom {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CaptchaCacheService captchaCacheService(BehaviorCaptchaProperties properties) {
|
||||
return ReflectUtil.newInstance(properties.getCacheImpl());
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
CaptchaServiceFactory.cacheService.put(StorageType.CUSTOM.name().toLowerCase(), SpringUtil.getBean(CaptchaCacheService.class));
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Behavior-CaptchaCache-Custom' completed initialization.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.charles7c.continew.starter.captcha.behavior.autoconfigure;
|
||||
|
||||
import com.anji.captcha.model.common.CaptchaTypeEnum;
|
||||
import com.anji.captcha.service.CaptchaCacheService;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.charles7c.continew.starter.captcha.behavior.enums.StorageType;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 行为验证码配置属性
|
||||
*
|
||||
* @author Bull-BCLS
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "continew-starter.captcha.behavior")
|
||||
public class BehaviorCaptchaProperties {
|
||||
|
||||
/**
|
||||
* 是否启用行为验证码
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 是否开启 AES 坐标加密(默认:true)
|
||||
*/
|
||||
private Boolean enableAes = true;
|
||||
|
||||
/**
|
||||
* 验证码类型(默认:滑动验证码)
|
||||
*/
|
||||
private CaptchaTypeEnum type = CaptchaTypeEnum.BLOCKPUZZLE;
|
||||
|
||||
/**
|
||||
* 缓存类型(默认:LOCAL 内存)
|
||||
*/
|
||||
private StorageType cacheType = StorageType.LOCAL;
|
||||
|
||||
/**
|
||||
* 自定义缓存类型(当 cacheType 为 CUSTOM 时必填)
|
||||
*/
|
||||
private Class<? extends CaptchaCacheService> cacheImpl;
|
||||
|
||||
/**
|
||||
* 滑动拼图底图路径(为空则使用默认底图)(路径下需要有两个文件夹,分别为 original(存放底图)slidingBlock(存放滑块))
|
||||
*/
|
||||
private String jigsawBaseMapPath;
|
||||
|
||||
/**
|
||||
* 校验滑动拼图允许误差偏移量(默认:5像素)
|
||||
*/
|
||||
private String slipOffset = "5";
|
||||
|
||||
/**
|
||||
* 点选文字底图路径(为空则使用默认底图)
|
||||
*/
|
||||
private String picClickBaseMapPath;
|
||||
|
||||
/**
|
||||
* 点选文字验证码的文字字体(默认:文泉驿正黑)
|
||||
*/
|
||||
private String fontType = "WenQuanZhengHei.ttf";
|
||||
|
||||
/**
|
||||
* 历史数据清除开关(0:关闭;1:开启)
|
||||
*/
|
||||
private Integer historyDataClearEnable = 0;
|
||||
|
||||
/**
|
||||
* 一分钟内接口请求次数限制开关(0:关闭;1:开启)
|
||||
*/
|
||||
private Integer reqFrequencyLimitEnable = 0;
|
||||
|
||||
/**
|
||||
* 一分钟内验证码最多失败次数限制(默认:5次)
|
||||
*/
|
||||
private int reqGetLockLimit = 5;
|
||||
|
||||
/**
|
||||
* 一分钟内验证码最多失败次数限制达标后锁定时间(默认:300秒)
|
||||
*/
|
||||
private int reqGetLockSeconds = 300;
|
||||
|
||||
/**
|
||||
* 获取验证码接口一分钟内请求次数限制(默认:100次)
|
||||
*/
|
||||
private int reqGetMinuteLimit = 100;
|
||||
|
||||
/**
|
||||
* 校验检验码接口一分内请求次数限制(默认:100次)
|
||||
*/
|
||||
private int reqCheckMinuteLimit = 100;
|
||||
|
||||
/**
|
||||
* 二次校验检验码接口一分钟内请求次数限制(默认:100次)
|
||||
*/
|
||||
private int reqVerifyMinuteLimit = 100;
|
||||
|
||||
/**
|
||||
* local缓存的阈值(默认:1000个)
|
||||
*/
|
||||
private String cacheNumber = "1000";
|
||||
|
||||
/**
|
||||
* 定时清理过期local缓存(默认:180秒)
|
||||
*/
|
||||
private String timingClear = "180";
|
||||
|
||||
/**
|
||||
* 右下角水印文字
|
||||
*/
|
||||
private String waterMark = "我的水印";
|
||||
|
||||
/**
|
||||
* 右下角水印字体(默认:文泉驿正黑)
|
||||
*/
|
||||
private String waterFont = "WenQuanZhengHei.ttf";
|
||||
|
||||
/**
|
||||
* 滑块干扰项(默认:0)
|
||||
*/
|
||||
private String interferenceOptions = "0";
|
||||
|
||||
/**
|
||||
* 点选字体样式(默认:BOLD)
|
||||
*/
|
||||
private int fontStyle = Font.BOLD;
|
||||
|
||||
/**
|
||||
* 点选字体大小(默认:25)
|
||||
*/
|
||||
private int fontSize = 25;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.captcha.behavior.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 缓存类型枚举
|
||||
*
|
||||
* @author Bull-BCLS
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Getter
|
||||
public enum StorageType {
|
||||
|
||||
/**
|
||||
* 内存
|
||||
*/
|
||||
LOCAL,
|
||||
|
||||
/**
|
||||
* Redis
|
||||
*/
|
||||
REDIS,
|
||||
|
||||
/**
|
||||
* 自定义
|
||||
*/
|
||||
CUSTOM,
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.captcha.behavior.impl;
|
||||
|
||||
import com.anji.captcha.service.CaptchaCacheService;
|
||||
import top.charles7c.continew.starter.cache.redisson.util.RedisUtils;
|
||||
import top.charles7c.continew.starter.captcha.behavior.enums.StorageType;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 行为验证码 Redis 缓存实现
|
||||
*
|
||||
* @author Bull-BCLS
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class BehaviorCaptchaCacheServiceImpl implements CaptchaCacheService {
|
||||
@Override
|
||||
public void set(String key, String value, long expiresInSeconds) {
|
||||
RedisUtils.set(key, value, Duration.ofSeconds(expiresInSeconds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String key) {
|
||||
return RedisUtils.hasKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String key) {
|
||||
RedisUtils.delete(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String key) {
|
||||
return RedisUtils.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return StorageType.REDIS.name().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.charles7c.continew.starter.captcha.behavior.autoconfigure.BehaviorCaptchaAutoConfiguration
|
||||
@@ -31,7 +31,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
@Slf4j
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(GraphicCaptchaProperties.class)
|
||||
@ConditionalOnProperty(prefix = "captcha.graphic", name = "enabled", havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = "continew-starter.captcha.graphic", name = "enabled", havingValue = "true")
|
||||
public class GraphicCaptchaAutoConfiguration {
|
||||
|
||||
@PostConstruct
|
||||
|
||||
@@ -21,7 +21,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.wf.captcha.base.Captcha;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.charles7c.continew.starter.captcha.graphic.enums.GraphicCaptchaTypeEnum;
|
||||
import top.charles7c.continew.starter.captcha.graphic.enums.GraphicCaptchaType;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@@ -32,7 +32,7 @@ import java.awt.*;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "captcha.graphic")
|
||||
@ConfigurationProperties(prefix = "continew-starter.captcha.graphic")
|
||||
public class GraphicCaptchaProperties {
|
||||
|
||||
/**
|
||||
@@ -43,7 +43,7 @@ public class GraphicCaptchaProperties {
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private GraphicCaptchaTypeEnum type;
|
||||
private GraphicCaptchaType type;
|
||||
|
||||
/**
|
||||
* 内容长度
|
||||
|
||||
@@ -29,7 +29,7 @@ import lombok.RequiredArgsConstructor;
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum GraphicCaptchaTypeEnum {
|
||||
public enum GraphicCaptchaType {
|
||||
|
||||
/**
|
||||
* 算术
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-captcha-graphic</module>
|
||||
<module>continew-starter-captcha-behavior</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -39,7 +39,7 @@ import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
@Lazy
|
||||
@AutoConfiguration
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnProperty(prefix = "cors", name = "enabled", havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = "continew-starter.cors", name = "enabled", havingValue = "true")
|
||||
@EnableConfigurationProperties(CorsProperties.class)
|
||||
public class CorsAutoConfiguration {
|
||||
|
||||
@@ -68,7 +68,7 @@ public class CorsAutoConfiguration {
|
||||
properties.getExposedHeaders().forEach(config::addExposedHeader);
|
||||
// 添加映射路径,拦截一切请求
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
source.registerCorsConfiguration(StringConstants.PATH_PATTERN, config);
|
||||
CorsFilter corsFilter = new CorsFilter(source);
|
||||
log.info("[ContiNew Starter] - Auto Configuration 'CorsFilter' completed initialization.");
|
||||
return corsFilter;
|
||||
|
||||
@@ -18,8 +18,10 @@ package top.charles7c.continew.starter.core.autoconfigure.cors;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -29,7 +31,7 @@ import java.util.List;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "cors")
|
||||
@ConfigurationProperties(prefix = "continew-starter.cors")
|
||||
public class CorsProperties {
|
||||
|
||||
/**
|
||||
@@ -40,20 +42,22 @@ public class CorsProperties {
|
||||
/**
|
||||
* 允许跨域的域名
|
||||
*/
|
||||
private List<String> allowedOrigins = new ArrayList<>();
|
||||
private List<String> allowedOrigins = new ArrayList<>(ALL);
|
||||
|
||||
/**
|
||||
* 允许跨域的请求方式
|
||||
*/
|
||||
private List<String> allowedMethods = new ArrayList<>();
|
||||
private List<String> allowedMethods = new ArrayList<>(ALL);
|
||||
|
||||
/**
|
||||
* 允许跨域的请求头
|
||||
*/
|
||||
private List<String> allowedHeaders = new ArrayList<>();
|
||||
private List<String> allowedHeaders = new ArrayList<>(ALL);
|
||||
|
||||
/**
|
||||
* 允许跨域的响应头
|
||||
*/
|
||||
private List<String> exposedHeaders = new ArrayList<>();
|
||||
|
||||
private static final List<String> ALL = Collections.singletonList(StringConstants.ASTERISK);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package top.charles7c.continew.starter.core.autoconfigure.project;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -82,9 +81,8 @@ public class ProjectProperties {
|
||||
public static final boolean IP_ADDR_LOCAL_PARSE_ENABLED;
|
||||
|
||||
static {
|
||||
String underlineCaseProperty = SpringUtil.getProperty("ip-addr-local-parse-enabled");
|
||||
String camelCaseProperty = SpringUtil.getProperty("ipAddrLocalParseEnabled");
|
||||
IP_ADDR_LOCAL_PARSE_ENABLED = Convert.toBool(underlineCaseProperty, false) || Convert.toBool(camelCaseProperty, false);
|
||||
IP_ADDR_LOCAL_PARSE_ENABLED = SpringUtil.getProperty("project.ip-addr-local-parse-enabled", boolean.class, false)
|
||||
|| SpringUtil.getProperty("project.ipAddrLocalParseEnabled", boolean.class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
@Lazy
|
||||
@AutoConfiguration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = "continew-starter.thread-pool", name = "enabled", havingValue = "true")
|
||||
@EnableAsync(proxyTargetClass = true)
|
||||
public class AsyncAutoConfiguration implements AsyncConfigurer {
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
@Slf4j
|
||||
@Lazy
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = "continew-starter.thread-pool", name = "enabled", havingValue = "true")
|
||||
@EnableConfigurationProperties(ThreadPoolProperties.class)
|
||||
public class ThreadPoolAutoConfiguration {
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "thread-pool")
|
||||
@ConfigurationProperties(prefix = "continew-starter.thread-pool")
|
||||
public class ThreadPoolProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,4 +58,9 @@ public class StringConstants implements StrPool {
|
||||
* 中文逗号
|
||||
*/
|
||||
public static final String CHINESE_COMMA = ",";
|
||||
|
||||
/**
|
||||
* 路径模式
|
||||
*/
|
||||
public static final String PATH_PATTERN = "/**";
|
||||
}
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.exception;
|
||||
package top.charles7c.continew.starter.core.exception;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import top.charles7c.continew.starter.core.exception.BaseException;
|
||||
|
||||
/**
|
||||
* 自定义验证异常-错误请求
|
||||
@@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.exception;
|
||||
package top.charles7c.continew.starter.core.exception;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import top.charles7c.continew.starter.core.exception.BaseException;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package top.charles7c.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
@@ -28,6 +30,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
|
||||
import net.dreamlu.mica.ip2region.core.IpInfo;
|
||||
import top.charles7c.continew.starter.core.autoconfigure.project.ProjectProperties;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* IP 工具类
|
||||
@@ -45,26 +50,26 @@ public class IpUtils {
|
||||
private static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true";
|
||||
|
||||
/**
|
||||
* 根据 IP 获取归属地信息
|
||||
* 查询 IP 归属地
|
||||
*
|
||||
* @param ip IP 地址
|
||||
* @return 归属地信息
|
||||
* @return IP 归属地
|
||||
*/
|
||||
public static String getCityInfo(String ip) {
|
||||
public static String getAddress(String ip) {
|
||||
if (ProjectProperties.IP_ADDR_LOCAL_PARSE_ENABLED) {
|
||||
return getLocalCityInfo(ip);
|
||||
return getAddressByLocal(ip);
|
||||
} else {
|
||||
return getHttpCityInfo(ip);
|
||||
return getAddressByHttp(ip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 IP 获取归属地信息(网络解析)
|
||||
* 查询 IP 归属地(网络解析)
|
||||
*
|
||||
* @param ip IP 地址
|
||||
* @return 归属地信息
|
||||
* @return IP 归属地
|
||||
*/
|
||||
public static String getHttpCityInfo(String ip) {
|
||||
public static String getAddressByHttp(String ip) {
|
||||
if (isInnerIp(ip)) {
|
||||
return "内网IP";
|
||||
}
|
||||
@@ -74,19 +79,21 @@ public class IpUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 IP 获取归属地信息(本地解析)
|
||||
* 查询 IP 归属地(本地库解析)
|
||||
*
|
||||
* @param ip IP 地址
|
||||
* @return 归属地信息
|
||||
* @return IP 归属地
|
||||
*/
|
||||
public static String getLocalCityInfo(String ip) {
|
||||
public static String getAddressByLocal(String ip) {
|
||||
if (isInnerIp(ip)) {
|
||||
return "内网IP";
|
||||
}
|
||||
Ip2regionSearcher ip2regionSearcher = SpringUtil.getBean(Ip2regionSearcher.class);
|
||||
IpInfo ipInfo = ip2regionSearcher.memorySearch(ip);
|
||||
if (null != ipInfo) {
|
||||
return ipInfo.getAddress();
|
||||
Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getAddress(), ipInfo.getIsp());
|
||||
regionSet.removeIf(StrUtil::isBlank);
|
||||
return String.join(StringConstants.SPACE, regionSet);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.util;
|
||||
package top.charles7c.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import lombok.AccessLevel;
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package top.charles7c.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -24,8 +25,9 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Servlet 工具类
|
||||
@@ -64,8 +66,57 @@ public class ServletUtils {
|
||||
if (null == request) {
|
||||
return null;
|
||||
}
|
||||
UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
|
||||
return userAgent.getBrowser().getName() + " " + userAgent.getVersion();
|
||||
return getBrowser(request.getHeader("User-Agent"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器及其版本信息
|
||||
*
|
||||
* @param userAgentString User-Agent 字符串
|
||||
* @return 浏览器及其版本信息
|
||||
*/
|
||||
public static String getBrowser(String userAgentString) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(userAgentString);
|
||||
return userAgent.getBrowser().getName() + StringConstants.SPACE + userAgent.getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作系统
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return 操作系统
|
||||
*/
|
||||
public static String getOs(HttpServletRequest request) {
|
||||
if (null == request) {
|
||||
return null;
|
||||
}
|
||||
return getOs(request.getHeader("User-Agent"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作系统
|
||||
*
|
||||
* @param userAgentString User-Agent 字符串
|
||||
* @return 操作系统
|
||||
*/
|
||||
public static String getOs(String userAgentString) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(userAgentString);
|
||||
return userAgent.getOs().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应所有的头(header)信息
|
||||
*
|
||||
* @param response 响应对象{@link HttpServletResponse}
|
||||
* @return header值
|
||||
*/
|
||||
public static Map<String, String> getHeaderMap(HttpServletResponse response) {
|
||||
final Collection<String> headerNames = response.getHeaderNames();
|
||||
final Map<String, String> headerMap = MapUtil.newHashMap(headerNames.size(), true);
|
||||
for (String name : headerNames) {
|
||||
headerMap.put(name, response.getHeader(name));
|
||||
}
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
private static ServletRequestAttributes getServletRequestAttributes() {
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.util.validate;
|
||||
package top.charles7c.continew.starter.core.util.validate;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
import top.charles7c.continew.starter.extension.crud.exception.BusinessException;
|
||||
import top.charles7c.continew.starter.core.exception.BusinessException;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.util.validate;
|
||||
package top.charles7c.continew.starter.core.util.validate;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.charles7c.continew.starter.extension.crud.exception.BadRequestException;
|
||||
import top.charles7c.continew.starter.core.exception.BadRequestException;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.util.validate;
|
||||
package top.charles7c.continew.starter.core.util.validate;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.data.mybatis.plus.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 是否启用数据权限注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@ConditionalOnProperty(prefix = "mybatis-plus.extension.data-permission", name = "enabled", havingValue = "true")
|
||||
public @interface ConditionalOnEnabledDataPermission {}
|
||||
@@ -17,7 +17,6 @@
|
||||
package top.charles7c.continew.starter.data.mybatis.plus.autoconfigure;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@@ -46,15 +45,28 @@ public class MyBatisPlusExtensionProperties {
|
||||
private String mapperPackage;
|
||||
|
||||
/**
|
||||
* 数据权限处理器实现类
|
||||
* 数据权限插件配置
|
||||
*/
|
||||
private Class<? extends DataPermissionHandler> dataPermissionHandlerImpl;
|
||||
private DataPermissionProperties dataPermission;
|
||||
|
||||
/**
|
||||
* 分页插件配置
|
||||
*/
|
||||
private PaginationProperties pagination;
|
||||
|
||||
/**
|
||||
* 数据权限插件配置属性
|
||||
*/
|
||||
@Data
|
||||
public static class DataPermissionProperties {
|
||||
|
||||
/**
|
||||
* 是否启用数据权限插件
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页插件配置属性
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package top.charles7c.continew.starter.data.mybatis.plus.autoconfigure;
|
||||
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
@@ -36,6 +36,9 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import top.charles7c.continew.starter.core.handler.GeneralPropertySourceFactory;
|
||||
import top.charles7c.continew.starter.data.mybatis.plus.datapermission.DataPermissionFilter;
|
||||
import top.charles7c.continew.starter.data.mybatis.plus.datapermission.DataPermissionHandlerImpl;
|
||||
|
||||
|
||||
/**
|
||||
* MyBatis Plus 自动配置
|
||||
@@ -60,13 +63,13 @@ public class MybatisPlusAutoConfiguration {
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor(MyBatisPlusExtensionProperties properties) {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 数据权限插件
|
||||
Class<? extends DataPermissionHandler> dataPermissionHandlerImpl = properties.getDataPermissionHandlerImpl();
|
||||
if (null != dataPermissionHandlerImpl) {
|
||||
interceptor.addInnerInterceptor(new DataPermissionInterceptor(ReflectUtil.newInstance(dataPermissionHandlerImpl)));
|
||||
MyBatisPlusExtensionProperties.DataPermissionProperties dataPermissionProperties = properties.getDataPermission();
|
||||
if (null != dataPermissionProperties && dataPermissionProperties.isEnabled()) {
|
||||
interceptor.addInnerInterceptor(new DataPermissionInterceptor(SpringUtil.getBean(DataPermissionHandler.class)));
|
||||
}
|
||||
// 分页插件
|
||||
MyBatisPlusExtensionProperties.PaginationProperties paginationProperties = properties.getPagination();
|
||||
if (properties.isEnabled() && paginationProperties.isEnabled()) {
|
||||
if (null != paginationProperties && paginationProperties.isEnabled()) {
|
||||
interceptor.addInnerInterceptor(this.paginationInnerInterceptor(paginationProperties));
|
||||
}
|
||||
// 防全表更新与删除插件
|
||||
@@ -74,6 +77,16 @@ public class MybatisPlusAutoConfiguration {
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限处理器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnEnabledDataPermission
|
||||
public DataPermissionHandler dataPermissionHandler(DataPermissionFilter dataPermissionFilter) {
|
||||
return new DataPermissionHandlerImpl(dataPermissionFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* ID 生成器配置(仅在主键类型(idType)配置为 ASSIGN_ID 或 ASSIGN_UUID 时有效)
|
||||
* <p>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.base;
|
||||
package top.charles7c.continew.starter.data.mybatis.plus.base;
|
||||
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.base;
|
||||
package top.charles7c.continew.starter.data.mybatis.plus.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.data.mybatis.plus.datapermission;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 数据权限注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DataPermission {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #tableAlias()} attribute.
|
||||
*/
|
||||
@AliasFor("tableAlias")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 表别名
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String tableAlias() default "";
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
String id() default "id";
|
||||
|
||||
/**
|
||||
* 部门 ID
|
||||
*/
|
||||
String deptId() default "dept_id";
|
||||
|
||||
/**
|
||||
* 用户 ID
|
||||
*/
|
||||
String userId() default "create_user";
|
||||
|
||||
/**
|
||||
* 角色 ID(角色和部门关联表)
|
||||
*/
|
||||
String roleId() default "role_id";
|
||||
|
||||
/**
|
||||
* 部门表别名
|
||||
*/
|
||||
String deptTableAlias() default "sys_dept";
|
||||
|
||||
/**
|
||||
* 角色和部门关联表别名
|
||||
*/
|
||||
String roleDeptTableAlias() default "sys_role_dept";
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.data.mybatis.plus.datapermission;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 当前用户信息
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Data
|
||||
public class DataPermissionCurrentUser {
|
||||
|
||||
/**
|
||||
* 用户 ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 角色列表
|
||||
*/
|
||||
private Set<CurrentUserRole> roles;
|
||||
|
||||
/**
|
||||
* 部门 ID
|
||||
*/
|
||||
private String deptId;
|
||||
|
||||
/**
|
||||
* 当前用户角色信息
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class CurrentUserRole {
|
||||
|
||||
/**
|
||||
* 角色 ID
|
||||
*/
|
||||
private String roleId;
|
||||
|
||||
/**
|
||||
* 数据权限
|
||||
*/
|
||||
private DataScope dataScope;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.data.mybatis.plus.datapermission;
|
||||
|
||||
/**
|
||||
* 数据权限过滤器接口
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface DataPermissionFilter {
|
||||
|
||||
/**
|
||||
* 是否过滤
|
||||
*
|
||||
* @return true:过滤;false:不过滤
|
||||
*/
|
||||
boolean isFilter();
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*
|
||||
* @return 当前用户信息
|
||||
*/
|
||||
DataPermissionCurrentUser getCurrentUser();
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.data.mybatis.plus.datapermission;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
|
||||
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.Function;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import net.sf.jsqlparser.expression.Parenthesis;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
|
||||
import net.sf.jsqlparser.statement.select.SubSelect;
|
||||
|
||||
/**
|
||||
* 数据权限处理器实现类
|
||||
*
|
||||
* @author <a href="https://gitee.com/baomidou/mybatis-plus/issues/I37I90">DataPermissionInterceptor 如何使用?</a>
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
|
||||
private final DataPermissionFilter dataPermissionFilter;
|
||||
|
||||
@Override
|
||||
public Expression getSqlSegment(Expression where, String mappedStatementId) {
|
||||
try {
|
||||
Class<?> clazz =
|
||||
Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StringConstants.DOT)));
|
||||
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringConstants.DOT) + 1);
|
||||
Method[] methodArr = clazz.getMethods();
|
||||
for (Method method : methodArr) {
|
||||
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
|
||||
if (null != dataPermission
|
||||
&& (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName))) {
|
||||
if (dataPermissionFilter.isFilter()) {
|
||||
return buildDataScopeFilter(dataPermission, where);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("Data permission handler build data scope filter occurred an error: {}.", e.getMessage(), e);
|
||||
}
|
||||
return where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建数据范围过滤条件
|
||||
*
|
||||
* @param dataPermission
|
||||
* 数据权限
|
||||
* @param where
|
||||
* 当前查询条件
|
||||
* @return 构建后查询条件
|
||||
*/
|
||||
private Expression buildDataScopeFilter(DataPermission dataPermission, Expression where) {
|
||||
Expression expression = null;
|
||||
String tableAlias = dataPermission.tableAlias();
|
||||
String id = dataPermission.id();
|
||||
String deptId = dataPermission.deptId();
|
||||
DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser();
|
||||
Set<DataPermissionCurrentUser.CurrentUserRole> roles = currentUser.getRoles();
|
||||
for (DataPermissionCurrentUser.CurrentUserRole role : roles) {
|
||||
DataScope dataScope = role.getDataScope();
|
||||
if (DataScope.ALL.equals(dataScope)) {
|
||||
return where;
|
||||
}
|
||||
if (DataScope.DEPT_AND_CHILD.equals(dataScope)) {
|
||||
// select t1.* from table as t1 where t1.`dept_id` in (select `id` from `sys_dept` where `id` = xxx or
|
||||
// find_in_set(xxx, `ancestors`));
|
||||
// 构建子查询
|
||||
SubSelect subSelect = new SubSelect();
|
||||
PlainSelect select = new PlainSelect();
|
||||
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(id))));
|
||||
select.setFromItem(new Table(dataPermission.deptTableAlias()));
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(new Column(id));
|
||||
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
|
||||
Function function = new Function();
|
||||
function.setName("find_in_set");
|
||||
function.setParameters(new ExpressionList(new LongValue(currentUser.getDeptId()), new Column("ancestors")));
|
||||
select.setWhere(new OrExpression(equalsTo, function));
|
||||
subSelect.setSelectBody(select);
|
||||
// 构建父查询
|
||||
InExpression inExpression = new InExpression();
|
||||
inExpression.setLeftExpression(this.buildColumn(tableAlias, deptId));
|
||||
inExpression.setRightExpression(subSelect);
|
||||
expression = null != expression ? new OrExpression(expression, inExpression) : inExpression;
|
||||
} else if (DataScope.DEPT.equals(dataScope)) {
|
||||
// select t1.* from table as t1 where t1.`dept_id` = xxx;
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(this.buildColumn(tableAlias, deptId));
|
||||
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
|
||||
expression = null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||
} else if (DataScope.SELF.equals(dataScope)) {
|
||||
// select t1.* from table as t1 where t1.`create_user` = xxx;
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(this.buildColumn(tableAlias, dataPermission.userId()));
|
||||
equalsTo.setRightExpression(new LongValue(currentUser.getUserId()));
|
||||
expression = null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||
} else if (DataScope.CUSTOM.equals(dataScope)) {
|
||||
// select t1.* from table as t1 where t1.`dept_id` in (select `dept_id` from `sys_role_dept` where
|
||||
// `role_id` = xxx);
|
||||
// 构建子查询
|
||||
SubSelect subSelect = new SubSelect();
|
||||
PlainSelect select = new PlainSelect();
|
||||
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(deptId))));
|
||||
select.setFromItem(new Table(dataPermission.roleDeptTableAlias()));
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
|
||||
equalsTo.setRightExpression(new LongValue(role.getRoleId()));
|
||||
select.setWhere(equalsTo);
|
||||
subSelect.setSelectBody(select);
|
||||
// 构建父查询
|
||||
InExpression inExpression = new InExpression();
|
||||
inExpression.setLeftExpression(this.buildColumn(tableAlias, deptId));
|
||||
inExpression.setRightExpression(subSelect);
|
||||
expression = null != expression ? new OrExpression(expression, inExpression) : inExpression;
|
||||
}
|
||||
}
|
||||
return null != where ? new AndExpression(where, new Parenthesis(expression)) : expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Column
|
||||
*
|
||||
* @param tableAlias
|
||||
* 表别名
|
||||
* @param columnName
|
||||
* 字段名称
|
||||
* @return 带表别名字段
|
||||
*/
|
||||
private Column buildColumn(String tableAlias, String columnName) {
|
||||
if (StringUtils.isNotEmpty(tableAlias)) {
|
||||
columnName = String.format("%s.%s", tableAlias, columnName);
|
||||
}
|
||||
return new Column(columnName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.data.mybatis.plus.datapermission;
|
||||
|
||||
/**
|
||||
* 数据权限枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public enum DataScope {
|
||||
|
||||
/**
|
||||
* 全部数据权限
|
||||
*/
|
||||
ALL,
|
||||
|
||||
/**
|
||||
* 本部门及以下数据权限
|
||||
*/
|
||||
DEPT_AND_CHILD,
|
||||
|
||||
/**
|
||||
* 本部门数据权限
|
||||
*/
|
||||
DEPT,
|
||||
|
||||
/**
|
||||
* 仅本人数据权限
|
||||
*/
|
||||
SELF,
|
||||
|
||||
/**
|
||||
* 自定义数据权限
|
||||
*/
|
||||
CUSTOM,
|
||||
}
|
||||
@@ -14,9 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.annotation;
|
||||
|
||||
import top.charles7c.continew.starter.extension.crud.enums.QueryTypeEnum;
|
||||
package top.charles7c.continew.starter.data.mybatis.plus.query;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@@ -40,7 +38,7 @@ public @interface Query {
|
||||
/**
|
||||
* 查询类型(等值查询、模糊查询、范围查询等)
|
||||
*/
|
||||
QueryTypeEnum type() default QueryTypeEnum.EQUAL;
|
||||
QueryType type() default QueryType.EQUAL;
|
||||
|
||||
/**
|
||||
* 多属性模糊查询,仅支持 String 类型属性
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.util;
|
||||
package top.charles7c.continew.starter.data.mybatis.plus.query;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
@@ -23,10 +23,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.charles7c.continew.starter.extension.crud.annotation.Query;
|
||||
import top.charles7c.continew.starter.extension.crud.enums.QueryTypeEnum;
|
||||
import top.charles7c.continew.starter.extension.crud.exception.BadRequestException;
|
||||
import top.charles7c.continew.starter.extension.crud.util.validate.ValidationUtils;
|
||||
import top.charles7c.continew.starter.core.exception.BadRequestException;
|
||||
import top.charles7c.continew.starter.core.util.ReflectUtils;
|
||||
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
@@ -129,7 +128,7 @@ public class QueryHelper {
|
||||
// 注意:数据库规范中列采用下划线连接法命名,程序规范中变量采用驼峰法命名
|
||||
String property = queryAnnotation.property();
|
||||
String columnName = StrUtil.toUnderlineCase(StrUtil.blankToDefault(property, fieldName));
|
||||
QueryTypeEnum queryType = queryAnnotation.type();
|
||||
QueryType queryType = queryAnnotation.type();
|
||||
switch (queryType) {
|
||||
case EQUAL -> queryWrapper.eq(columnName, fieldValue);
|
||||
case NOT_EQUAL -> queryWrapper.ne(columnName, fieldValue);
|
||||
@@ -14,11 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.extension.crud.enums;
|
||||
package top.charles7c.continew.starter.data.mybatis.plus.query;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import top.charles7c.continew.starter.extension.crud.base.IBaseEnum;
|
||||
|
||||
/**
|
||||
* 查询类型枚举
|
||||
@@ -28,7 +27,7 @@ import top.charles7c.continew.starter.extension.crud.base.IBaseEnum;
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum QueryTypeEnum implements IBaseEnum<Integer> {
|
||||
public enum QueryType {
|
||||
|
||||
/**
|
||||
* 等值查询,例如:WHERE `age` = 18
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>3.1.5</version>
|
||||
<version>3.1.7</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
@@ -54,19 +54,23 @@
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<revision>1.0.0</revision>
|
||||
<just-auth.version>1.16.5</just-auth.version>
|
||||
<revision>1.1.0</revision>
|
||||
<just-auth.version>1.16.6</just-auth.version>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<mybatis-plus.version>3.5.4.1</mybatis-plus.version>
|
||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||
<dynamic-datasource.version>4.2.0</dynamic-datasource.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<redisson.version>3.24.3</redisson.version>
|
||||
<redisson.version>3.25.2</redisson.version>
|
||||
<sms4j.version>3.0.4</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.2</easy-excel.version>
|
||||
<knife4j.version>4.3.0</knife4j.version>
|
||||
<ip2region.version>3.1.5.1</ip2region.version>
|
||||
<hutool.version>5.8.23</hutool.version>
|
||||
<easy-excel.version>3.3.3</easy-excel.version>
|
||||
<x-file-storage.version>2.0.0</x-file-storage.version>
|
||||
<aws-s3.version>1.12.626</aws-s3.version>
|
||||
<knife4j.version>4.4.0</knife4j.version>
|
||||
<ttl.version>2.14.4</ttl.version>
|
||||
<ip2region.version>3.1.6</ip2region.version>
|
||||
<hutool.version>5.8.24</hutool.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -148,6 +152,13 @@
|
||||
<version>${sms4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AJ-Captcha(行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式) -->
|
||||
<dependency>
|
||||
<groupId>com.anji-plus</groupId>
|
||||
<artifactId>captcha</artifactId>
|
||||
<version>${aj-captcha.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Easy Captcha(Java 图形验证码,支持 gif、中文、算术等类型,可用于 Java Web、JavaSE 等项目) -->
|
||||
<dependency>
|
||||
<groupId>com.github.whvcse</groupId>
|
||||
@@ -162,6 +173,20 @@
|
||||
<version>${easy-excel.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- X File Storage(一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台) -->
|
||||
<dependency>
|
||||
<groupId>org.dromara.x-file-storage</groupId>
|
||||
<artifactId>x-file-storage-spring</artifactId>
|
||||
<version>${x-file-storage.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Amazon S3(Amazon Simple Storage Service,亚马逊简单存储服务,通用存储协议 S3,兼容主流云厂商对象存储) -->
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>${aws-s3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Knife4j(前身是 swagger-bootstrap-ui,集 Swagger2 和 OpenAPI3 为一体的增强解决方案) -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
@@ -171,6 +196,13 @@
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- TTL(线程间传递 ThreadLocal,异步执行时上下文传递的解决方案) -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
<version>${ttl.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 第三方封装 Ip2region(离线 IP 数据管理框架和定位库,支持亿级别的数据段,10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) -->
|
||||
<dependency>
|
||||
<groupId>net.dreamlu</groupId>
|
||||
@@ -235,6 +267,13 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 验证码模块 - 行为验证码 -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-captcha-behavior</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 验证码模块 - 图形验证码 -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
@@ -249,6 +288,27 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 存储模块 - 本地存储 -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-storage-local</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 定制增强版) -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-log-httptrace-pro</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - 公共模块 -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-log-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- API 文档模块 -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
@@ -274,6 +334,7 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 扁平化 Maven 插件(统一版本号) -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
|
||||
@@ -23,17 +23,18 @@ import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorContro
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import top.charles7c.continew.starter.extension.crud.handler.GlobalErrorHandler;
|
||||
import top.charles7c.continew.starter.extension.crud.handler.GlobalExceptionHandler;
|
||||
|
||||
/**
|
||||
* 全局错误处理器自动配置
|
||||
* 全局异常处理器自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@AutoConfiguration
|
||||
@Import(GlobalErrorHandler.class)
|
||||
@Import({GlobalExceptionHandler.class, GlobalErrorHandler.class})
|
||||
@ConditionalOnMissingBean(BasicErrorController.class)
|
||||
@ComponentScan("top.charles7c.continew.starter.extension.crud.handler")
|
||||
public class GlobalErrorHandlerAutoConfiguration {
|
||||
public class GlobalExceptionHandlerAutoConfiguration {
|
||||
}
|
||||
@@ -34,14 +34,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
||||
import top.charles7c.continew.starter.core.util.ReflectUtils;
|
||||
import top.charles7c.continew.starter.core.util.validate.CheckUtils;
|
||||
import top.charles7c.continew.starter.data.mybatis.plus.base.BaseMapper;
|
||||
import top.charles7c.continew.starter.data.mybatis.plus.query.QueryHelper;
|
||||
import top.charles7c.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.charles7c.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.charles7c.continew.starter.extension.crud.model.resp.PageDataResp;
|
||||
import top.charles7c.continew.starter.extension.crud.util.QueryHelper;
|
||||
import top.charles7c.continew.starter.extension.crud.util.ReflectUtils;
|
||||
import top.charles7c.continew.starter.extension.crud.util.TreeUtils;
|
||||
import top.charles7c.continew.starter.extension.crud.util.validate.CheckUtils;
|
||||
import top.charles7c.continew.starter.file.excel.util.ExcelUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -162,7 +163,6 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long add(C req) {
|
||||
if (null == req) {
|
||||
return 0L;
|
||||
@@ -173,7 +173,6 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(C req, Long id) {
|
||||
T entity = this.getById(id);
|
||||
BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue());
|
||||
|
||||
@@ -49,11 +49,11 @@ public class CrudRequestMappingHandlerMapping extends RequestMappingHandlerMappi
|
||||
if (!handlerType.isAnnotationPresent(CrudRequestMapping.class)) {
|
||||
return requestMappingInfo;
|
||||
}
|
||||
// 过滤 API,如果 API 列表中不包含,则忽略
|
||||
CrudRequestMapping crudRequestMapping = handlerType.getDeclaredAnnotation(CrudRequestMapping.class);
|
||||
// 过滤 API,如果非本类中定义,且 API 列表中不包含,则忽略
|
||||
Api[] apiArr = crudRequestMapping.api();
|
||||
Api api = ExceptionUtils.exToNull(() -> Api.valueOf(method.getName().toUpperCase()));
|
||||
if (!ArrayUtil.containsAny(apiArr, Api.ALL, api)) {
|
||||
if (method.getDeclaringClass() != handlerType && !ArrayUtil.containsAny(apiArr, Api.ALL, api)) {
|
||||
return null;
|
||||
}
|
||||
// 拼接路径(合并了 @RequestMapping 的部分能力)
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.extension.crud.handler;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
import top.charles7c.continew.starter.core.exception.BadRequestException;
|
||||
import top.charles7c.continew.starter.core.exception.BusinessException;
|
||||
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 拦截自定义验证异常-错误请求
|
||||
*/
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
|
||||
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-违反约束异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
|
||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||
String errorMsg =
|
||||
CollUtil.join(e.getConstraintViolations(), StringConstants.CHINESE_COMMA, ConstraintViolation::getMessage);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-绑定异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public R handleBindException(BindException e, HttpServletRequest request) {
|
||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||
String errorMsg = CollUtil.join(e.getAllErrors(), StringConstants.CHINESE_COMMA,
|
||||
DefaultMessageSourceResolvable::getDefaultMessage);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-方法参数无效异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
|
||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||
String errorMsg = ExceptionUtils
|
||||
.exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-方法参数类型不匹配异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
|
||||
HttpServletRequest request) {
|
||||
String errorMsg = StrUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter().getParameterType());
|
||||
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截文件上传异常-超过上传大小限制
|
||||
*/
|
||||
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||
public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
|
||||
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
|
||||
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
|
||||
String errorMsg = String.format("请上传小于 %sMB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证异常-登录认证
|
||||
*/
|
||||
@ExceptionHandler(NotLoginException.class)
|
||||
public R handleNotLoginException(NotLoginException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],认证失败,无法访问系统资源。", request.getRequestURI(), e);
|
||||
String errorMsg = switch (e.getType()) {
|
||||
case NotLoginException.KICK_OUT -> "您已被踢下线。";
|
||||
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线。";
|
||||
default -> "您的登录状态已过期,请重新登录。";
|
||||
};
|
||||
return R.fail(HttpStatus.UNAUTHORIZED.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证异常-权限认证
|
||||
*/
|
||||
@ExceptionHandler(NotPermissionException.class)
|
||||
public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],权限码校验失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.FORBIDDEN.value(), "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证异常-角色认证
|
||||
*/
|
||||
@ExceptionHandler(NotRoleException.class)
|
||||
public R handleNotRoleException(NotRoleException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],角色权限校验失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.FORBIDDEN.value(), "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-请求方式不支持异常
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],不支持 [{}] 请求。", request.getRequestURI(), e.getMethod());
|
||||
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public R handleServiceException(BusinessException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截未知的运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截未知的系统异常
|
||||
*/
|
||||
@ExceptionHandler(Throwable.class)
|
||||
public R handleException(Throwable e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ import cn.hutool.core.lang.tree.parser.NodeParser;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import top.charles7c.continew.starter.core.util.validate.CheckUtils;
|
||||
import top.charles7c.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.charles7c.continew.starter.extension.crud.util.validate.CheckUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
top.charles7c.continew.starter.extension.crud.autoconfigure.CrudAutoConfiguration
|
||||
top.charles7c.continew.starter.extension.crud.autoconfigure.GlobalErrorHandlerAutoConfiguration
|
||||
top.charles7c.continew.starter.extension.crud.autoconfigure.GlobalExceptionHandlerAutoConfiguration
|
||||
17
continew-starter-log/continew-starter-log-common/pom.xml
Normal file
17
continew-starter-log/continew-starter-log-common/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-log</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-common</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 日志模块 - 公共模块</description>
|
||||
</project>
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 日志注解
|
||||
* <p>用于接口方法或类上,辅助 Spring Doc 使用效果最佳</p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Log {
|
||||
|
||||
/**
|
||||
* 日志描述(仅用于接口方法上)
|
||||
* <p>
|
||||
* 优先级:@Log("描述") > @Operation(summary="描述")
|
||||
* </p>
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 所属模块(用于接口方法或类上)
|
||||
* <p>
|
||||
* 优先级: 接口方法上的 @Log(module = "模块") > 接口类上的 @Log(module = "模块") > @Tag(name = "模块") 内容
|
||||
* </p>
|
||||
*/
|
||||
String module() default "";
|
||||
|
||||
/**
|
||||
* 是否忽略日志记录(用于接口方法或类上)
|
||||
*/
|
||||
boolean ignore() default false;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.charles7c.continew.starter.log.common.dao;
|
||||
|
||||
import top.charles7c.continew.starter.log.common.model.LogRecord;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日志持久层接口
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface LogDao {
|
||||
|
||||
/**
|
||||
* 查询日志列表
|
||||
*
|
||||
* @return 日志列表
|
||||
*/
|
||||
default List<LogRecord> list() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志
|
||||
*
|
||||
* @param logRecord 日志信息
|
||||
*/
|
||||
void add(LogRecord logRecord);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.common.dao.impl;
|
||||
|
||||
import top.charles7c.continew.starter.log.common.dao.LogDao;
|
||||
import top.charles7c.continew.starter.log.common.model.LogRecord;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日志持久层接口默认实现类(基于内存)
|
||||
*
|
||||
* @author Dave Syer(Spring Boot Actuator)
|
||||
* @author Olivier Bourgain(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class LogDaoDefaultImpl implements LogDao {
|
||||
|
||||
/**
|
||||
* 容量
|
||||
*/
|
||||
private int capacity = 100;
|
||||
|
||||
/**
|
||||
* 是否降序
|
||||
*/
|
||||
private boolean reverse = true;
|
||||
|
||||
/**
|
||||
* 日志列表
|
||||
*/
|
||||
private final List<LogRecord> logRecords = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public List<LogRecord> list() {
|
||||
synchronized (this.logRecords) {
|
||||
return List.copyOf(this.logRecords);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(LogRecord logRecord) {
|
||||
synchronized (this.logRecords) {
|
||||
while (this.logRecords.size() >= this.capacity) {
|
||||
this.logRecords.remove(this.reverse ? this.capacity - 1 : 0);
|
||||
}
|
||||
if (this.reverse) {
|
||||
this.logRecords.add(0, logRecord);
|
||||
} else {
|
||||
this.logRecords.add(logRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内存中存储的最大日志容量
|
||||
*
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public void setCapacity(int capacity) {
|
||||
synchronized (this.logRecords) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否降序
|
||||
*
|
||||
* @param reverse 是否降序(默认:true)
|
||||
*/
|
||||
public void setReverse(boolean reverse) {
|
||||
synchronized (this.logRecords) {
|
||||
this.reverse = reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.common.enums;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志包含信息
|
||||
*
|
||||
* @author Wallace Wadge(Spring Boot Actuator)
|
||||
* @author Emily Tsanova(Spring Boot Actuator)
|
||||
* @author Joseph Beeton(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public enum Include {
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
DESCRIPTION,
|
||||
|
||||
/**
|
||||
* 模块
|
||||
*/
|
||||
MODULE,
|
||||
|
||||
/**
|
||||
* 请求头(默认)
|
||||
*/
|
||||
REQUEST_HEADERS,
|
||||
|
||||
/**
|
||||
* 请求体(如包含请求体,则请求参数无效)
|
||||
*/
|
||||
REQUEST_BODY,
|
||||
|
||||
/**
|
||||
* 请求参数(默认)
|
||||
*/
|
||||
REQUEST_PARAM,
|
||||
|
||||
/**
|
||||
* IP 归属地
|
||||
*/
|
||||
IP_ADDRESS,
|
||||
|
||||
/**
|
||||
* 浏览器
|
||||
*/
|
||||
BROWSER,
|
||||
|
||||
/**
|
||||
* 操作系统
|
||||
*/
|
||||
OS,
|
||||
|
||||
/**
|
||||
* 响应头(默认)
|
||||
*/
|
||||
RESPONSE_HEADERS,
|
||||
|
||||
/**
|
||||
* 响应体(如包含响应体,则响应参数无效)
|
||||
*/
|
||||
RESPONSE_BODY,
|
||||
|
||||
/**
|
||||
* 响应参数(默认)
|
||||
*/
|
||||
RESPONSE_PARAM,
|
||||
;
|
||||
|
||||
private static final Set<Include> DEFAULT_INCLUDES;
|
||||
|
||||
static {
|
||||
Set<Include> defaultIncludes = new LinkedHashSet<>();
|
||||
defaultIncludes.add(Include.REQUEST_HEADERS);
|
||||
defaultIncludes.add(Include.RESPONSE_HEADERS);
|
||||
defaultIncludes.add(Include.REQUEST_PARAM);
|
||||
defaultIncludes.add(Include.RESPONSE_PARAM);
|
||||
DEFAULT_INCLUDES = Collections.unmodifiableSet(defaultIncludes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认包含信息
|
||||
*
|
||||
* @return 默认包含信息
|
||||
*/
|
||||
public static Set<Include> defaultIncludes() {
|
||||
return DEFAULT_INCLUDES;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.common.model;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import top.charles7c.continew.starter.log.common.enums.Include;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志信息
|
||||
*
|
||||
* @author Dave Syer(Spring Boot Actuator)
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Phillip Webb(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Data
|
||||
public class LogRecord {
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 模块
|
||||
*/
|
||||
private String module;
|
||||
|
||||
/**
|
||||
* 请求信息
|
||||
*/
|
||||
private LogRequest request;
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
*/
|
||||
private LogResponse response;
|
||||
|
||||
/**
|
||||
* 耗时
|
||||
*/
|
||||
private Duration timeTaken;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private final Instant timestamp;
|
||||
|
||||
public LogRecord(Instant timestamp, LogRequest request, LogResponse response, Duration timeTaken) {
|
||||
this.timestamp = timestamp;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.timeTaken = timeTaken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始记录日志
|
||||
*
|
||||
* @param request 请求信息
|
||||
* @return 日志记录器
|
||||
*/
|
||||
public static Started start(RecordableHttpRequest request) {
|
||||
return start(Clock.systemUTC(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始记录日志
|
||||
*
|
||||
* @param timestamp 开始时间
|
||||
* @param request 请求信息
|
||||
* @return 日志记录器
|
||||
*/
|
||||
public static Started start(Clock timestamp, RecordableHttpRequest request) {
|
||||
return new Started(timestamp, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志记录器
|
||||
*/
|
||||
public static final class Started {
|
||||
|
||||
private final Instant timestamp;
|
||||
|
||||
private final RecordableHttpRequest request;
|
||||
|
||||
private Started(Clock clock, RecordableHttpRequest request) {
|
||||
this.timestamp = Instant.now(clock);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束日志记录
|
||||
*
|
||||
* @param response 响应信息
|
||||
* @param includes 包含信息
|
||||
* @return 日志记录
|
||||
*/
|
||||
public LogRecord finish(RecordableHttpResponse response, Set<Include> includes) {
|
||||
return finish(Clock.systemUTC(), response, includes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束日志记录
|
||||
*
|
||||
* @param clock 时间
|
||||
* @param response 响应信息
|
||||
* @param includes 包含信息
|
||||
* @return 日志记录
|
||||
*/
|
||||
public LogRecord finish(Clock clock, RecordableHttpResponse response, Set<Include> includes) {
|
||||
LogRequest logRequest = new LogRequest(this.request, includes);
|
||||
LogResponse logResponse = new LogResponse(response, includes);
|
||||
Duration duration = Duration.between(this.timestamp, Instant.now(clock));
|
||||
return new LogRecord(this.timestamp, logRequest, logResponse, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.common.model;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Data;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
||||
import top.charles7c.continew.starter.core.util.IpUtils;
|
||||
import top.charles7c.continew.starter.core.util.ServletUtils;
|
||||
import top.charles7c.continew.starter.log.common.enums.Include;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 请求信息
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Data
|
||||
public class LogRequest {
|
||||
|
||||
/**
|
||||
* 请求方式
|
||||
*/
|
||||
private String method;
|
||||
|
||||
/**
|
||||
* 请求 URL
|
||||
*/
|
||||
private URI url;
|
||||
|
||||
/**
|
||||
* IP
|
||||
*/
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 请求头
|
||||
*/
|
||||
private Map<String, String> headers;
|
||||
|
||||
/**
|
||||
* 请求体(JSON 字符串)
|
||||
*/
|
||||
private String body;
|
||||
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private Map<String, Object> param;
|
||||
|
||||
/**
|
||||
* IP 归属地
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 浏览器
|
||||
*/
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 操作系统
|
||||
*/
|
||||
private String os;
|
||||
|
||||
public LogRequest(RecordableHttpRequest request, Set<Include> includes) {
|
||||
this.method = request.getMethod();
|
||||
this.url = request.getUrl();
|
||||
this.ip = request.getIp();
|
||||
this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? request.getHeaders() : null;
|
||||
if (includes.contains(Include.REQUEST_BODY)) {
|
||||
this.body = request.getBody();
|
||||
} else if (includes.contains(Include.REQUEST_PARAM)) {
|
||||
this.param = request.getParam();
|
||||
}
|
||||
this.address = (includes.contains(Include.IP_ADDRESS)) ? IpUtils.getAddress(this.ip) : null;
|
||||
String userAgentString = ExceptionUtils.exToNull(() -> this.headers.get(HttpHeaders.USER_AGENT));
|
||||
if (StrUtil.isNotBlank(userAgentString)) {
|
||||
this.browser = (includes.contains(Include.BROWSER)) ? ServletUtils.getBrowser(userAgentString) : null;
|
||||
this.os = (includes.contains(Include.OS)) ? ServletUtils.getOs(userAgentString) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
import top.charles7c.continew.starter.log.common.enums.Include;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Data
|
||||
public class LogResponse {
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 响应头
|
||||
*/
|
||||
private Map<String, String> headers;
|
||||
|
||||
/**
|
||||
* 响应体(JSON 字符串)
|
||||
*/
|
||||
private String body;
|
||||
|
||||
/**
|
||||
* 响应参数
|
||||
*/
|
||||
private Map<String, Object> param;
|
||||
|
||||
public LogResponse(RecordableHttpResponse response, Set<Include> includes) {
|
||||
this.status = response.getStatus();
|
||||
this.headers = (includes.contains(Include.RESPONSE_HEADERS)) ? response.getHeaders() : null;
|
||||
if (includes.contains(Include.RESPONSE_BODY)) {
|
||||
this.body = response.getBody();
|
||||
} else if (includes.contains(Include.RESPONSE_PARAM)) {
|
||||
this.param = response.getParam();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.common.model;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 请求信息
|
||||
*
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Phillip Webb(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @see RecordableHttpResponse
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface RecordableHttpRequest {
|
||||
|
||||
/**
|
||||
* 获取请求方式
|
||||
*
|
||||
* @return 请求方式
|
||||
*/
|
||||
String getMethod();
|
||||
|
||||
/**
|
||||
* 获取 URL
|
||||
*
|
||||
* @return URL
|
||||
*/
|
||||
URI getUrl();
|
||||
|
||||
/**
|
||||
* 获取 IP
|
||||
*
|
||||
* @return IP
|
||||
*/
|
||||
String getIp();
|
||||
|
||||
/**
|
||||
* 获取请求头
|
||||
*
|
||||
* @return 请求头
|
||||
*/
|
||||
Map<String, String> getHeaders();
|
||||
|
||||
/**
|
||||
* 获取请求体
|
||||
*
|
||||
* @return 请求体
|
||||
*/
|
||||
String getBody();
|
||||
|
||||
/**
|
||||
* 获取请求参数
|
||||
*
|
||||
* @return 请求参数
|
||||
*/
|
||||
Map<String, Object> getParam();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.common.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 响应信息
|
||||
*
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @see RecordableHttpRequest
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface RecordableHttpResponse {
|
||||
|
||||
/**
|
||||
* 获取状态码
|
||||
*
|
||||
* @return 状态码
|
||||
*/
|
||||
int getStatus();
|
||||
|
||||
/**
|
||||
* 获取响应头
|
||||
*
|
||||
* @return 响应头
|
||||
*/
|
||||
Map<String, String> getHeaders();
|
||||
|
||||
/**
|
||||
* 获取响应体
|
||||
*
|
||||
* @return 响应体
|
||||
*/
|
||||
String getBody();
|
||||
|
||||
/**
|
||||
* 获取响应参数
|
||||
*
|
||||
* @return 响应参数
|
||||
*/
|
||||
Map<String, Object> getParam();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-log</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-httptrace-pro</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 日志模块 - HttpTracePro(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>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - 公共模块 -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-log-common</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.httptracepro.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 是否启用日志记录注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@ConditionalOnProperty(prefix = "continew-starter.log", name = "enabled", havingValue = "true")
|
||||
public @interface ConditionalOnEnabledLog {}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.httptracepro.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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 org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.charles7c.continew.starter.log.common.dao.LogDao;
|
||||
import top.charles7c.continew.starter.log.common.dao.impl.LogDaoDefaultImpl;
|
||||
import top.charles7c.continew.starter.log.httptracepro.handler.LogFilter;
|
||||
import top.charles7c.continew.starter.log.httptracepro.handler.LogInterceptor;
|
||||
|
||||
/**
|
||||
* 日志自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConditionalOnEnabledLog
|
||||
@RequiredArgsConstructor
|
||||
@EnableConfigurationProperties(LogProperties.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private final LogProperties properties;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LogInterceptor(logDao(), properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志过滤器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogFilter logFilter() {
|
||||
return new LogFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志持久层接口
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogDao logDao() {
|
||||
return new LogDaoDefaultImpl();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.info("[ContiNew Starter] - Auto Configuration 'Log-HttpTracePro' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.httptracepro.autoconfigure;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.charles7c.continew.starter.log.common.enums.Include;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "continew-starter.log")
|
||||
public class LogProperties {
|
||||
|
||||
/**
|
||||
* 是否启用日志
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 是否打印日志,开启后可打印访问日志(类似于 Nginx access log)
|
||||
*/
|
||||
private Boolean isPrint = false;
|
||||
|
||||
/**
|
||||
* 包含信息
|
||||
*/
|
||||
private Set<Include> include = new HashSet<>(Include.defaultIncludes());
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.httptracepro.handler;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
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 java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 日志过滤器
|
||||
*
|
||||
* @author Dave Syer(Spring Boot Actuator)
|
||||
* @author Wallace Wadge(Spring Boot Actuator)
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Venil Noronha(Spring Boot Actuator)
|
||||
* @author Madhura Bhave(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class LogFilter extends OncePerRequestFilter implements Ordered {
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE - 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
if (!isRequestValid(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
// 包装输入、输出流,可重复读取
|
||||
if (!(request instanceof ContentCachingRequestWrapper)) {
|
||||
request = new ContentCachingRequestWrapper(request);
|
||||
}
|
||||
if (!(response instanceof ContentCachingResponseWrapper)) {
|
||||
response = new ContentCachingResponseWrapper(response);
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
// 更新响应(不操作这一步,会导致接口响应空白)
|
||||
updateResponse(response);
|
||||
}
|
||||
|
||||
private boolean isRequestValid(HttpServletRequest request) {
|
||||
try {
|
||||
new URI(request.getRequestURL().toString());
|
||||
return true;
|
||||
} catch (URISyntaxException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateResponse(HttpServletResponse response) throws IOException {
|
||||
ContentCachingResponseWrapper responseWrapper =
|
||||
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
||||
Objects.requireNonNull(responseWrapper).copyBodyToResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.httptracepro.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||
import top.charles7c.continew.starter.log.common.dao.LogDao;
|
||||
import top.charles7c.continew.starter.log.common.enums.Include;
|
||||
import top.charles7c.continew.starter.log.common.model.LogRecord;
|
||||
import top.charles7c.continew.starter.log.common.model.LogResponse;
|
||||
import top.charles7c.continew.starter.log.httptracepro.autoconfigure.LogProperties;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LogInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final LogDao dao;
|
||||
private final LogProperties properties;
|
||||
private final TransmittableThreadLocal<LogRecord.Started> timestampTtl = new TransmittableThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
||||
@NonNull Object handler) {
|
||||
Clock timestamp = Clock.systemUTC();
|
||||
if (this.isRequestRecord(handler, request)) {
|
||||
if (Boolean.TRUE.equals(properties.getIsPrint())) {
|
||||
log.info("[{}] {}", request.getMethod(), request.getRequestURI());
|
||||
}
|
||||
LogRecord.Started startedLogRecord = LogRecord.start(timestamp, new RecordableServletHttpRequest(request));
|
||||
timestampTtl.set(startedLogRecord);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
||||
@NonNull Object handler, Exception e) {
|
||||
LogRecord.Started startedLogRecord = timestampTtl.get();
|
||||
if (null == startedLogRecord) {
|
||||
return;
|
||||
}
|
||||
timestampTtl.remove();
|
||||
Set<Include> includeSet = properties.getInclude();
|
||||
try {
|
||||
LogRecord finishedLogRecord = startedLogRecord.finish(new RecordableServletHttpResponse(response, response.getStatus()), includeSet);
|
||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||
// 记录日志描述
|
||||
if (includeSet.contains(Include.DESCRIPTION)) {
|
||||
this.logDescription(finishedLogRecord, handlerMethod);
|
||||
}
|
||||
// 记录所属模块
|
||||
if (includeSet.contains(Include.MODULE)) {
|
||||
this.logModule(finishedLogRecord, handlerMethod);
|
||||
}
|
||||
if (Boolean.TRUE.equals(properties.getIsPrint())) {
|
||||
LogResponse logResponse = finishedLogRecord.getResponse();
|
||||
log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), logResponse.getStatus(), finishedLogRecord.getTimeTaken().toMillis());
|
||||
}
|
||||
dao.add(finishedLogRecord);
|
||||
} catch (Exception ex) {
|
||||
log.error("Logging http log occurred an error: {}.", ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录描述
|
||||
*
|
||||
* @param logRecord 日志信息
|
||||
* @param handlerMethod 处理器方法
|
||||
*/
|
||||
private void logDescription(LogRecord logRecord, HandlerMethod handlerMethod) {
|
||||
// 例如:@Operation(summary="新增部门") -> 新增部门
|
||||
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
|
||||
if (null != methodOperation) {
|
||||
logRecord.setDescription(StrUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述"));
|
||||
}
|
||||
// 例如:@Log("新增部门") -> 新增部门
|
||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
||||
if (null != methodLog && StrUtil.isNotBlank(methodLog.value())) {
|
||||
logRecord.setDescription(methodLog.value());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录模块
|
||||
*
|
||||
* @param logRecord 日志信息
|
||||
* @param handlerMethod 处理器方法
|
||||
*/
|
||||
private void logModule(LogRecord logRecord, HandlerMethod handlerMethod) {
|
||||
// 例如:@Tag(name = "部门管理") -> 部门管理
|
||||
Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class);
|
||||
if (null != classTag) {
|
||||
String name = classTag.name();
|
||||
logRecord.setModule(StrUtil.blankToDefault(name, "请在该接口类上指定所属模块"));
|
||||
}
|
||||
// 例如:@Log(module = "部门管理") -> 部门管理
|
||||
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
|
||||
if (null != classLog && StrUtil.isNotBlank(classLog.module())) {
|
||||
logRecord.setModule(classLog.module());
|
||||
}
|
||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
||||
if (null != methodLog && StrUtil.isNotBlank(methodLog.module())) {
|
||||
logRecord.setModule(methodLog.module());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否要记录日志
|
||||
*
|
||||
* @param handler 处理器
|
||||
* @param request 请求对象
|
||||
* @return true:需要记录;false:不需要记录
|
||||
*/
|
||||
private boolean isRequestRecord(Object handler, HttpServletRequest request) {
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return false;
|
||||
}
|
||||
// 不拦截 /error
|
||||
ServerProperties serverProperties = SpringUtil.getBean(ServerProperties.class);
|
||||
if (request.getRequestURI().equals(serverProperties.getError().getPath())) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口被隐藏,不记录日志
|
||||
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
|
||||
if (null != methodOperation && methodOperation.hidden()) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口方法或类上有 @Log 注解,且要求忽略该接口,则不记录日志
|
||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
||||
if (null != methodLog && methodLog.ignore()) {
|
||||
return false;
|
||||
}
|
||||
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
|
||||
return null == classLog || !classLog.ignore();
|
||||
}
|
||||
}
|
||||
@@ -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.charles7c.continew.starter.log.httptracepro.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
import top.charles7c.continew.starter.log.common.model.RecordableHttpRequest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 请求信息适配器
|
||||
*
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
*/
|
||||
public final class RecordableServletHttpRequest implements RecordableHttpRequest {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
public RecordableServletHttpRequest(HttpServletRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return request.getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUrl() {
|
||||
String queryString = request.getQueryString();
|
||||
if (StrUtil.isBlank(queryString)) {
|
||||
return URI.create(request.getRequestURL().toString());
|
||||
}
|
||||
try {
|
||||
StringBuffer urlBuffer = this.appendQueryString(queryString);
|
||||
return new URI(urlBuffer.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8);
|
||||
StringBuffer urlBuffer = this.appendQueryString(encoded);
|
||||
return URI.create(urlBuffer.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIp() {
|
||||
return JakartaServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getHeaders() {
|
||||
return JakartaServletUtil.getHeaderMap(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
|
||||
if (null != wrapper) {
|
||||
String body = StrUtil.utf8Str(wrapper.getContentAsByteArray());
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getParam() {
|
||||
String body = this.getBody();
|
||||
return StrUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
|
||||
? JSONUtil.toBean(body, Map.class)
|
||||
: Collections.unmodifiableMap(request.getParameterMap());
|
||||
}
|
||||
|
||||
private StringBuffer appendQueryString(String queryString) {
|
||||
return request.getRequestURL().append(StringConstants.QUESTION_MARK).append(queryString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.log.httptracepro.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
import top.charles7c.continew.starter.core.util.ServletUtils;
|
||||
import top.charles7c.continew.starter.log.common.model.RecordableHttpResponse;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 响应信息适配器
|
||||
*
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
*/
|
||||
public final class RecordableServletHttpResponse implements RecordableHttpResponse {
|
||||
|
||||
private final HttpServletResponse response;
|
||||
|
||||
private final int status;
|
||||
|
||||
public RecordableServletHttpResponse(HttpServletResponse response, int status) {
|
||||
this.response = response;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getHeaders() {
|
||||
return ServletUtils.getHeaderMap(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
||||
if (null != wrapper) {
|
||||
return StrUtil.utf8Str(wrapper.getContentAsByteArray());
|
||||
}
|
||||
return StringConstants.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getParam() {
|
||||
String body = this.getBody();
|
||||
return StrUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
|
||||
? JSONUtil.toBean(body, Map.class)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.charles7c.continew.starter.log.httptracepro.autoconfigure.LogAutoConfiguration
|
||||
30
continew-starter-log/pom.xml
Normal file
30
continew-starter-log/pom.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 日志模块</description>
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-log-common</module>
|
||||
<module>continew-starter-log-httptrace-pro</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-storage</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-storage-local</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 存储模块 - 本地存储</description>
|
||||
</project>
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.storage.local.autoconfigure;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* 本地文件自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableWebMvc
|
||||
@AutoConfiguration
|
||||
@RequiredArgsConstructor
|
||||
@EnableConfigurationProperties(LocalStorageProperties.class)
|
||||
@ConditionalOnProperty(name = "continew-starter.storage.local.enabled", havingValue = "true")
|
||||
public class LocalStorageAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private final LocalStorageProperties properties;
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
Map<String, LocalStorageProperties.LocalStorageMapping> mappingMap = properties.getMapping();
|
||||
for (Map.Entry<String, LocalStorageProperties.LocalStorageMapping> mappingEntry : mappingMap.entrySet()) {
|
||||
LocalStorageProperties.LocalStorageMapping mapping = mappingEntry.getValue();
|
||||
String pathPattern = mapping.getPathPattern();
|
||||
String location = mapping.getLocation();
|
||||
if (StrUtil.isBlank(location)) {
|
||||
throw new IllegalArgumentException(String.format("Path pattern [%s] location is null.", pathPattern));
|
||||
}
|
||||
registry.addResourceHandler(StrUtil.appendIfMissing(pathPattern, StringConstants.PATH_PATTERN))
|
||||
.addResourceLocations(!location.startsWith("file:") ? String.format("file:%s", this.format(location)) : this.format(location))
|
||||
.setCachePeriod(0);
|
||||
}
|
||||
}
|
||||
|
||||
private String format(String location) {
|
||||
return location.replace(StringConstants.BACKSLASH, StringConstants.SLASH);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.info("[ContiNew Starter] - Auto Configuration 'Storage-Local' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.charles7c.continew.starter.storage.local.autoconfigure;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 本地存储配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "continew-starter.storage.local")
|
||||
public class LocalStorageProperties {
|
||||
|
||||
/**
|
||||
* 是否启用本地存储
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 存储映射
|
||||
*/
|
||||
private Map<String, LocalStorageMapping> mapping = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 本地存储映射
|
||||
*/
|
||||
@Data
|
||||
public static class LocalStorageMapping {
|
||||
|
||||
/**
|
||||
* 路径模式
|
||||
*/
|
||||
private String pathPattern;
|
||||
|
||||
/**
|
||||
* 资源路径
|
||||
*/
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 单文件上传大小限制
|
||||
*/
|
||||
private DataSize maxFileSize = DataSize.ofMegabytes(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.charles7c.continew.starter.storage.local.autoconfigure.LocalStorageAutoConfiguration
|
||||
29
continew-starter-storage/pom.xml
Normal file
29
continew-starter-storage/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-storage</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 存储模块</description>
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-storage-local</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
5
pom.xml
5
pom.xml
@@ -72,6 +72,8 @@
|
||||
<module>continew-starter-core</module>
|
||||
<module>continew-starter-json</module>
|
||||
<module>continew-starter-api-doc</module>
|
||||
<module>continew-starter-log</module>
|
||||
<module>continew-starter-storage</module>
|
||||
<module>continew-starter-file</module>
|
||||
<module>continew-starter-captcha</module>
|
||||
<module>continew-starter-cache</module>
|
||||
@@ -92,7 +94,7 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 代码等格式化插件 -->
|
||||
<!-- 代码格式化插件 -->
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
@@ -114,6 +116,7 @@
|
||||
</java>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- 扁平化 Maven 插件(统一版本号) -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user