mirror of
https://github.com/continew-org/continew-admin.git
synced 2026-01-14 06:57:11 +08:00
build: continew-starter 2.14.0 => 2.15.0
1.适配 cs Knife4j 替换为 NextDoc4j 1.1.5 2.适配 cs continew-starter-auth-justauth 3.cs DataPermission 注解增加缓存处理,缓存 Mapper 接口方法上携带 DataPermission 的值 4.cs 修复开启 SSL 后,mail.host 不被 JDK 信任的问题 5.cs EnumValue 比较枚举值时,不再区分大小写 6.cs 依赖全面升级 spring-boot 3.3.12 => 3.4.10,snail-job 1.5.0 => 1.8.0
This commit is contained in:
72
README.md
72
README.md
@@ -4,7 +4,7 @@
|
|||||||
<img src="https://img.shields.io/badge/SNAPSHOT-v4.2.0-%23ff3f59.svg" alt="Release" />
|
<img src="https://img.shields.io/badge/SNAPSHOT-v4.2.0-%23ff3f59.svg" alt="Release" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/continew-org/continew-starter" title="ContiNew Starter" target="_blank">
|
<a href="https://github.com/continew-org/continew-starter" title="ContiNew Starter" target="_blank">
|
||||||
<img src="https://img.shields.io/badge/ContiNew Starter-2.14.0-%236CB52D.svg" alt="ContiNew Starter" />
|
<img src="https://img.shields.io/badge/ContiNew Starter-2.15.0-%236CB52D.svg" alt="ContiNew Starter" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://spring.io/projects/spring-boot" title="Spring Boot" target="_blank">
|
<a href="https://spring.io/projects/spring-boot" title="Spring Boot" target="_blank">
|
||||||
<img src="https://img.shields.io/badge/Spring Boot-3.3.12-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
|
<img src="https://img.shields.io/badge/Spring Boot-3.3.12-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
|
||||||
@@ -234,41 +234,41 @@ public class DeptController extends BaseController<DeptService, DeptResp, DeptDe
|
|||||||
|
|
||||||
## 核心技术栈
|
## 核心技术栈
|
||||||
|
|
||||||
| 名称 | 版本 | 简介 |
|
| 名称 | 版本 | 简介 |
|
||||||
| :----------------------------------------------------------- |:-------------| :----------------------------------------------------------- |
|
|:----------------------------------------------------------------------------------------------------------------------------------|:-------------| :----------------------------------------------------------- |
|
||||||
| <a href="https://vuejs.org/" target="_blank">Vue</a> | 3.5.4 | 渐进式 JavaScript 框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 |
|
| <a href="https://vuejs.org/" target="_blank">Vue</a> | 3.5.4 | 渐进式 JavaScript 框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 |
|
||||||
| <a href="https://arco.design/vue/docs/start" target="_blank">Arco Design</a> | 2.57.0 | 字节跳动推出的前端 UI 框架,年轻化的色彩和组件设计。 |
|
| <a href="https://arco.design/vue/docs/start" target="_blank">Arco Design</a> | 2.57.0 | 字节跳动推出的前端 UI 框架,年轻化的色彩和组件设计。 |
|
||||||
| <a href="https://www.typescriptlang.org/zh/" target="_blank">TypeScript</a> | 5.0.4 | TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。 |
|
| <a href="https://www.typescriptlang.org/zh/" target="_blank">TypeScript</a> | 5.0.4 | TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。 |
|
||||||
| <a href="https://vite.dev/" target="_blank">Vite</a> | 5.1.5 | 下一代的前端工具链,为开发提供极速响应。 |
|
| <a href="https://vite.dev/" target="_blank">Vite</a> | 5.1.5 | 下一代的前端工具链,为开发提供极速响应。 |
|
||||||
| [ContiNew Starter](https://github.com/continew-org/continew-starter) | 2.14.0 | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken),可轻松集成到应用中,为开发人员减少手动引入依赖及配置的麻烦,为 Spring Boot Web 项目的灵活快速构建提供支持。 |
|
| [ContiNew Starter](https://github.com/continew-org/continew-starter) | 2.15.0 | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken),可轻松集成到应用中,为开发人员减少手动引入依赖及配置的麻烦,为 Spring Boot Web 项目的灵活快速构建提供支持。 |
|
||||||
| <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a> | 3.3.12 | 简化 Spring 应用的初始搭建和开发过程,基于“约定优于配置”的理念,使开发人员不再需要定义样板化的配置。(Spring Boot 3.0 开始,要求 Java 17 作为最低版本) |
|
| <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a> | 3.3.12 | 简化 Spring 应用的初始搭建和开发过程,基于“约定优于配置”的理念,使开发人员不再需要定义样板化的配置。(Spring Boot 3.0 开始,要求 Java 17 作为最低版本) |
|
||||||
| <a href="https://undertow.io/" target="_blank">Undertow</a> | 2.3.18.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 |
|
| <a href="https://undertow.io/" target="_blank">Undertow</a> | 2.3.18.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 |
|
||||||
| <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token + JWT</a> | 1.44.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 |
|
| <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token + JWT</a> | 1.44.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 |
|
||||||
| <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a> | 3.5.12 | MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率。 |
|
| <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a> | 3.5.12 | MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率。 |
|
||||||
| <a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a> | 4.3.1 | 基于 Spring Boot 的快速集成多数据源的启动器。 |
|
| <a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a> | 4.3.1 | 基于 Spring Boot 的快速集成多数据源的启动器。 |
|
||||||
| Hikari | 5.1.0 | JDBC 连接池,号称 “史上最快连接池”,SpringBoot 在 2.0 之后,采用的默认数据库连接池就是 Hikari。 |
|
| Hikari | 5.1.0 | JDBC 连接池,号称 “史上最快连接池”,SpringBoot 在 2.0 之后,采用的默认数据库连接池就是 Hikari。 |
|
||||||
| <a href="https://dev.mysql.com/downloads/mysql/" target="_blank">MySQL</a> | 8.0.42 | 体积小、速度快、总体拥有成本低,是最流行的关系型数据库管理系统之一。 |
|
| <a href="https://dev.mysql.com/downloads/mysql/" target="_blank">MySQL</a> | 8.0.42 | 体积小、速度快、总体拥有成本低,是最流行的关系型数据库管理系统之一。 |
|
||||||
| <a href="https://dev.mysql.com/doc/connector-j/8.0/en/" target="_blank">mysql-connector-j</a> | 8.3.0 | MySQL Java 驱动。 |
|
| <a href="https://dev.mysql.com/doc/connector-j/8.0/en/" target="_blank">mysql-connector-j</a> | 8.3.0 | MySQL Java 驱动。 |
|
||||||
| <a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a> | 3.9.1 | SQL 性能分析组件。 |
|
| <a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a> | 3.9.1 | SQL 性能分析组件。 |
|
||||||
| <a href="https://github.com/liquibase/liquibase" target="_blank">Liquibase</a> | 4.27.0 | 用于管理数据库版本,跟踪、管理和应用数据库变化。 |
|
| <a href="https://github.com/liquibase/liquibase" target="_blank">Liquibase</a> | 4.27.0 | 用于管理数据库版本,跟踪、管理和应用数据库变化。 |
|
||||||
| [JetCache](https://github.com/alibaba/jetcache/blob/master/docs/CN/Readme.md) | 2.7.8 | 一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作。 |
|
| [JetCache](https://github.com/alibaba/jetcache/blob/master/docs/CN/Readme.md) | 2.7.8 | 一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作。 |
|
||||||
| <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.49.0 | 不仅仅是一个 Redis Java 客户端,Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,为使用者提供了一系列具有分布式特性的常用工具:分布式锁、限流器等。 |
|
| <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.49.0 | 不仅仅是一个 Redis Java 客户端,Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,为使用者提供了一系列具有分布式特性的常用工具:分布式锁、限流器等。 |
|
||||||
| <a href="https://redis.io/" target="_blank">Redis</a> | 7.2.8 | 高性能的 key-value 数据库。 |
|
| <a href="https://redis.io/" target="_blank">Redis</a> | 7.2.8 | 高性能的 key-value 数据库。 |
|
||||||
| [Snail Job](https://snailjob.opensnail.com/) | 1.5.0 | 灵活,可靠和快速的分布式任务重试和分布式任务调度平台。 |
|
| [Snail Job](https://snailjob.opensnail.com/) | 1.5.0 | 灵活,可靠和快速的分布式任务重试和分布式任务调度平台。 |
|
||||||
| [X File Storage](https://x-file-storage.xuyanwu.cn/#/) | 2.2.1 | 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台。 |
|
| [X File Storage](https://x-file-storage.xuyanwu.cn/#/) | 2.2.1 | 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台。 |
|
||||||
| <a href="https://sms4j.com/" target="_blank">SMS4J</a> | 3.3.4 | 短信聚合框架,轻松集成多家短信服务,解决接入多个短信 SDK 的繁琐流程。 |
|
| <a href="https://sms4j.com/" target="_blank">SMS4J</a> | 3.3.4 | 短信聚合框架,轻松集成多家短信服务,解决接入多个短信 SDK 的繁琐流程。 |
|
||||||
| <a href="https://justauth.cn/" target="_blank">Just Auth</a> | 1.16.7 | 开箱即用的整合第三方登录的开源组件,脱离繁琐的第三方登录 SDK,让登录变得 So easy! |
|
| <a href="https://justauth.cn/" target="_blank">Just Auth</a> | 1.16.7 | 开箱即用的整合第三方登录的开源组件,脱离繁琐的第三方登录 SDK,让登录变得 So easy! |
|
||||||
| <a href="https://github.com/fast-excel/fastexcel" target="_blank">Fast Excel</a> | 1.2.0 | (由原 EasyExcel 作者创建的新项目)一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。 |
|
| <a href="https://github.com/fast-excel/fastexcel" target="_blank">Fast Excel</a> | 1.2.0 | (由原 EasyExcel 作者创建的新项目)一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。 |
|
||||||
| [AJ-Captcha](https://ajcaptcha.beliefteam.cn/captcha-doc/) | 1.3.0 | Java 行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式。 |
|
| [AJ-Captcha](https://ajcaptcha.beliefteam.cn/captcha-doc/) | 1.3.0 | Java 行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式。 |
|
||||||
| Easy Captcha | 1.6.2 | Java 图形验证码,支持 gif、中文、算术等类型,可用于 Java Web、JavaSE 等项目。 |
|
| Easy Captcha | 1.6.2 | Java 图形验证码,支持 gif、中文、算术等类型,可用于 Java Web、JavaSE 等项目。 |
|
||||||
| [Crane4j](https://createsequence.gitee.io/crane4j-doc/#/) | 2.9.0 | 一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架。 |
|
| [Crane4j](https://createsequence.gitee.io/crane4j-doc/#/) | 2.9.0 | 一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架。 |
|
||||||
| [SpEL Validator](https://spel-validator.sticki.cn/) | 0.5.2-beta | 基于 SpEL 的 jakarta.validation-api 扩展增强包。 |
|
| [SpEL Validator](https://spel-validator.sticki.cn/) | 0.5.2-beta | 基于 SpEL 的 jakarta.validation-api 扩展增强包。 |
|
||||||
| [CosID](https://cosid.ahoo.me/guide/getting-started.html) | 2.13.0 | 旨在提供通用、灵活、高性能的分布式 ID 生成器。 |
|
| [CosID](https://cosid.ahoo.me/guide/getting-started.html) | 2.13.0 | 旨在提供通用、灵活、高性能的分布式 ID 生成器。 |
|
||||||
| [Graceful Response](https://doc.feiniaojin.com/graceful-response/home.html) | 5.0.4-boot3 | 一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量。 |
|
| [Graceful Response](https://doc.feiniaojin.com/graceful-response/home.html) | 5.0.4-boot3 | 一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量。 |
|
||||||
| <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a> | 4.5.0 | 前身是 swagger-bootstrap-ui,集 Swagger2 和 OpenAPI3 为一体的增强解决方案。 |
|
| <a href="https://nextdoc4j.top/" target="_blank">NextDoc4j</a> | 1.1.5 | 现代化 API 文档 UI 工具,全面替代 Swagger UI。 |
|
||||||
| [OpenFeign](https://springdoc.cn/spring-cloud-openfeign/) | 13.5 | Spring Cloud OpenFeign 是一种基于 Spring Cloud 的声明式 REST 客户端,它简化了与 HTTP 服务交互的过程。 |
|
| [OpenFeign](https://springdoc.cn/spring-cloud-openfeign/) | 13.5 | Spring Cloud OpenFeign 是一种基于 Spring Cloud 的声明式 REST 客户端,它简化了与 HTTP 服务交互的过程。 |
|
||||||
| <a href="https://www.hutool.cn/" target="_blank">Hutool</a> | 5.8.38 | 小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅,让 Java 语言也可以“甜甜的”。 |
|
| <a href="https://www.hutool.cn/" target="_blank">Hutool</a> | 5.8.38 | 小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅,让 Java 语言也可以“甜甜的”。 |
|
||||||
| <a href="https://projectlombok.org/" target="_blank">Lombok</a> | 1.18.36 | 在 Java 开发过程中用注解的方式,简化了 JavaBean 的编写,避免了冗余和样板式代码,让编写的类更加简洁。 |
|
| <a href="https://projectlombok.org/" target="_blank">Lombok</a> | 1.18.36 | 在 Java 开发过程中用注解的方式,简化了 JavaBean 的编写,避免了冗余和样板式代码,让编写的类更加简洁。 |
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
@@ -550,7 +550,7 @@ ContiNew 系列项目采用清晰的分支策略,确保开发与维护有序
|
|||||||
### 特别鸣谢
|
### 特别鸣谢
|
||||||
|
|
||||||
- 感谢 <a href="https://www.jetbrains.com/" target="_blank">JetBrains</a> 提供的 <a href="https://jb.gg/OpenSourceSupport" target="_blank">非商业开源软件开发授权</a>
|
- 感谢 <a href="https://www.jetbrains.com/" target="_blank">JetBrains</a> 提供的 <a href="https://jb.gg/OpenSourceSupport" 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/alibaba/jetcache" target="_blank">JetCache</a>、<a href="https://github.com/opengoofy/crane4j" target="_blank">Crane4j</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/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/alibaba/jetcache" target="_blank">JetCache</a>、<a href="https://github.com/opengoofy/crane4j" target="_blank">Crane4j</a>、<a href="https://nextdoc4j.top/" target="_blank">NextDoc4j</a>、<a href="https://github.com/dromara/hutool" target="_blank">Hutool</a> 等开源组件作者为国内开源世界作出的贡献
|
||||||
- 感谢项目使用或未使用到的每一款开源组件,致敬各位开源先驱 :fire:
|
- 感谢项目使用或未使用到的每一款开源组件,致敬各位开源先驱 :fire:
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -101,6 +101,11 @@
|
|||||||
<groupId>cn.dev33</groupId>
|
<groupId>cn.dev33</groupId>
|
||||||
<artifactId>sa-token-sign</artifactId>
|
<artifactId>sa-token-sign</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- NextDoc4j - SaToken 权限展示插件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>top.nextdoc4j</groupId>
|
||||||
|
<artifactId>nextdoc4j-plugin-security-satoken</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- ContiNew Starter 认证模块 - JustAuth -->
|
<!-- ContiNew Starter 认证模块 - JustAuth -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -1,217 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package top.continew.admin.common.config.doc;
|
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
|
||||||
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.PathItem;
|
|
||||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
|
||||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
|
||||||
import org.springframework.aop.support.AopUtils;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import top.continew.starter.apidoc.autoconfigure.SpringDocExtensionProperties;
|
|
||||||
import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties;
|
|
||||||
import top.continew.starter.core.util.CollUtils;
|
|
||||||
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局鉴权参数定制器
|
|
||||||
*
|
|
||||||
* @author echo
|
|
||||||
* @since 2024/12/31 13:36
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class GlobalAuthenticationCustomizer implements GlobalOpenApiCustomizer {
|
|
||||||
|
|
||||||
private final SpringDocExtensionProperties properties;
|
|
||||||
private final SaTokenExtensionProperties saTokenExtensionProperties;
|
|
||||||
private final ApplicationContext context;
|
|
||||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 定制 OpenAPI 文档
|
|
||||||
*
|
|
||||||
* @param openApi 当前 OpenAPI 对象
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void customise(OpenAPI openApi) {
|
|
||||||
if (MapUtil.isEmpty(openApi.getPaths())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 收集需要排除的路径(包括 Sa-Token 配置中的排除路径和 @SaIgnore 注解路径)
|
|
||||||
Set<String> excludedPaths = collectExcludedPaths();
|
|
||||||
|
|
||||||
// 遍历所有路径,为需要鉴权的路径添加安全认证配置
|
|
||||||
openApi.getPaths().forEach((path, pathItem) -> {
|
|
||||||
if (isPathExcluded(path, excludedPaths)) {
|
|
||||||
// 路径在排除列表中,跳过处理
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 为路径添加安全认证参数
|
|
||||||
addAuthenticationParameters(pathItem);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集所有需要排除的路径
|
|
||||||
*
|
|
||||||
* @return 排除路径集合
|
|
||||||
*/
|
|
||||||
private Set<String> collectExcludedPaths() {
|
|
||||||
Set<String> excludedPaths = new HashSet<>();
|
|
||||||
excludedPaths.addAll(Arrays.asList(saTokenExtensionProperties.getSecurity().getExcludes()));
|
|
||||||
excludedPaths.addAll(resolveSaIgnorePaths());
|
|
||||||
return excludedPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为路径项添加认证参数
|
|
||||||
*
|
|
||||||
* @param pathItem 当前路径项
|
|
||||||
*/
|
|
||||||
private void addAuthenticationParameters(PathItem pathItem) {
|
|
||||||
Components components = properties.getComponents();
|
|
||||||
if (components == null || MapUtil.isEmpty(components.getSecuritySchemes())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Map<String, SecurityScheme> securitySchemes = components.getSecuritySchemes();
|
|
||||||
List<String> schemeNames = CollUtils.mapToList(securitySchemes.values(), SecurityScheme::getName);
|
|
||||||
pathItem.readOperations().forEach(operation -> {
|
|
||||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
|
||||||
schemeNames.forEach(securityRequirement::addList);
|
|
||||||
operation.addSecurityItem(securityRequirement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析所有带有 @SaIgnore 注解的路径
|
|
||||||
*
|
|
||||||
* @return 被忽略的路径集合
|
|
||||||
*/
|
|
||||||
private Set<String> resolveSaIgnorePaths() {
|
|
||||||
// 获取所有标注 @RestController 的 Bean
|
|
||||||
Map<String, Object> controllers = context.getBeansWithAnnotation(RestController.class);
|
|
||||||
Set<String> ignoredPaths = new HashSet<>();
|
|
||||||
|
|
||||||
// 遍历所有控制器,解析 @SaIgnore 注解路径
|
|
||||||
controllers.values().forEach(controllerBean -> {
|
|
||||||
Class<?> controllerClass = AopUtils.getTargetClass(controllerBean);
|
|
||||||
List<String> classPaths = getClassPaths(controllerClass);
|
|
||||||
|
|
||||||
// 类级别的 @SaIgnore 注解
|
|
||||||
if (controllerClass.isAnnotationPresent(SaIgnore.class)) {
|
|
||||||
classPaths.forEach(classPath -> ignoredPaths.add(classPath + "/**"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法级别的 @SaIgnore 注解
|
|
||||||
Arrays.stream(controllerClass.getDeclaredMethods())
|
|
||||||
.filter(method -> method.isAnnotationPresent(SaIgnore.class))
|
|
||||||
.forEach(method -> ignoredPaths.addAll(combinePaths(classPaths, getMethodPaths(method))));
|
|
||||||
});
|
|
||||||
|
|
||||||
return ignoredPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取类上的所有路径
|
|
||||||
*
|
|
||||||
* @param controller 控制器类
|
|
||||||
* @return 类路径列表
|
|
||||||
*/
|
|
||||||
private List<String> getClassPaths(Class<?> controller) {
|
|
||||||
List<String> classPaths = new ArrayList<>();
|
|
||||||
// 处理 @RequestMapping 注解
|
|
||||||
if (controller.isAnnotationPresent(RequestMapping.class)) {
|
|
||||||
RequestMapping mapping = controller.getAnnotation(RequestMapping.class);
|
|
||||||
classPaths.addAll(Arrays.asList(mapping.value()));
|
|
||||||
}
|
|
||||||
// 处理 @CrudRequestMapping 注解
|
|
||||||
if (controller.isAnnotationPresent(CrudRequestMapping.class)) {
|
|
||||||
CrudRequestMapping mapping = controller.getAnnotation(CrudRequestMapping.class);
|
|
||||||
if (!mapping.value().isEmpty()) {
|
|
||||||
classPaths.add(mapping.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return classPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取方法上的所有路径
|
|
||||||
*
|
|
||||||
* @param method 控制器方法
|
|
||||||
* @return 方法路径列表
|
|
||||||
*/
|
|
||||||
private List<String> getMethodPaths(Method method) {
|
|
||||||
List<String> methodPaths = new ArrayList<>();
|
|
||||||
|
|
||||||
// 检查方法上的各种映射注解
|
|
||||||
if (method.isAnnotationPresent(GetMapping.class)) {
|
|
||||||
methodPaths.addAll(Arrays.asList(method.getAnnotation(GetMapping.class).value()));
|
|
||||||
} else if (method.isAnnotationPresent(PostMapping.class)) {
|
|
||||||
methodPaths.addAll(Arrays.asList(method.getAnnotation(PostMapping.class).value()));
|
|
||||||
} else if (method.isAnnotationPresent(PutMapping.class)) {
|
|
||||||
methodPaths.addAll(Arrays.asList(method.getAnnotation(PutMapping.class).value()));
|
|
||||||
} else if (method.isAnnotationPresent(DeleteMapping.class)) {
|
|
||||||
methodPaths.addAll(Arrays.asList(method.getAnnotation(DeleteMapping.class).value()));
|
|
||||||
} else if (method.isAnnotationPresent(RequestMapping.class)) {
|
|
||||||
methodPaths.addAll(Arrays.asList(method.getAnnotation(RequestMapping.class).value()));
|
|
||||||
} else if (method.isAnnotationPresent(PatchMapping.class)) {
|
|
||||||
methodPaths.addAll(Arrays.asList(method.getAnnotation(PatchMapping.class).value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return methodPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组合类路径和方法路径
|
|
||||||
*
|
|
||||||
* @param classPaths 类路径列表
|
|
||||||
* @param methodPaths 方法路径列表
|
|
||||||
* @return 完整路径集合
|
|
||||||
*/
|
|
||||||
private Set<String> combinePaths(List<String> classPaths, List<String> methodPaths) {
|
|
||||||
return classPaths.stream()
|
|
||||||
.flatMap(classPath -> methodPaths.stream().map(methodPath -> classPath + methodPath))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查路径是否在排除列表中
|
|
||||||
*
|
|
||||||
* @param path 当前路径
|
|
||||||
* @param excludedPaths 排除路径集合,支持通配符
|
|
||||||
* @return 是否匹配排除规则
|
|
||||||
*/
|
|
||||||
private boolean isPathExcluded(String path, Set<String> excludedPaths) {
|
|
||||||
return excludedPaths.stream().anyMatch(pattern -> pathMatcher.match(pattern, path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package top.continew.admin.common.config.doc;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import io.swagger.v3.oas.models.Operation;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springdoc.core.customizers.GlobalOperationCustomizer;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.method.HandlerMethod;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局描述定制器 - 处理 sa-token 的注解权限码
|
|
||||||
*
|
|
||||||
* @author echo
|
|
||||||
* @since 2025/1/24 14:59
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class GlobalDescriptionCustomizer implements GlobalOperationCustomizer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
|
|
||||||
// 将 sa-token 注解数据添加到 operation 的描述中
|
|
||||||
// 权限
|
|
||||||
List<String> noteList = new ArrayList<>(new OperationDescriptionCustomizer().getPermission(handlerMethod));
|
|
||||||
|
|
||||||
// 如果注解数据列表为空,直接返回原 operation
|
|
||||||
if (noteList.isEmpty()) {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
// 拼接注解数据为字符串
|
|
||||||
String noteStr = StrUtil.join("<br/>", noteList);
|
|
||||||
// 获取原描述
|
|
||||||
String originalDescription = operation.getDescription();
|
|
||||||
// 根据原描述是否为空,更新描述
|
|
||||||
String newDescription = StrUtil.isNotEmpty(originalDescription)
|
|
||||||
? originalDescription + "<br/>" + noteStr
|
|
||||||
: noteStr;
|
|
||||||
|
|
||||||
// 设置新描述
|
|
||||||
operation.setDescription(newDescription);
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.continew.admin.common.config.doc;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ClassUtil;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import io.swagger.v3.oas.models.Components;
|
||||||
|
import io.swagger.v3.oas.models.media.Content;
|
||||||
|
import io.swagger.v3.oas.models.media.Schema;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.apache.commons.lang3.reflect.TypeUtils;
|
||||||
|
import org.springdoc.core.properties.SpringDocConfigProperties;
|
||||||
|
import org.springdoc.core.service.GenericResponseService;
|
||||||
|
import org.springdoc.core.service.OperationService;
|
||||||
|
import org.springdoc.core.utils.PropertyResolverUtils;
|
||||||
|
import org.springdoc.core.utils.SpringDocAnnotationsUtils;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.springdoc.core.converters.ConverterUtils.isResponseTypeWrapper;
|
||||||
|
import static org.springdoc.core.utils.SpringDocAnnotationsUtils.extractSchema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局响应操作自定义器
|
||||||
|
* <p>
|
||||||
|
* 自定义 OpenAPI 文档中的响应结构,将原始返回类型包装为统一的响应格式
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author echo
|
||||||
|
* @since 2025/07/08 09:34
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class GlobalSpringDocResponseOperationCustomizer extends GenericResponseService {
|
||||||
|
|
||||||
|
private final GlobalResponseProperties globalResponseProperties;
|
||||||
|
private final Class<Object> responseClass;
|
||||||
|
private final PropertyResolverUtils propertyResolverUtils;
|
||||||
|
|
||||||
|
public GlobalSpringDocResponseOperationCustomizer(OperationService operationService,
|
||||||
|
SpringDocConfigProperties springDocConfigProperties,
|
||||||
|
PropertyResolverUtils propertyResolverUtils,
|
||||||
|
GlobalResponseProperties globalResponseProperties) {
|
||||||
|
super(operationService, springDocConfigProperties, propertyResolverUtils);
|
||||||
|
this.globalResponseProperties = globalResponseProperties;
|
||||||
|
this.responseClass = ClassUtil.loadClass(globalResponseProperties.getResponseClassFullName());
|
||||||
|
this.propertyResolverUtils = propertyResolverUtils;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建响应内容
|
||||||
|
*
|
||||||
|
* @param components 组件信息
|
||||||
|
* @param annotations 方法注解
|
||||||
|
* @param methodProduces 方法支持的媒体类型
|
||||||
|
* @param jsonView JSON 视图
|
||||||
|
* @param returnType 返回类型
|
||||||
|
* @return 响应内容
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Content buildContent(Components components,
|
||||||
|
Annotation[] annotations,
|
||||||
|
String[] methodProduces,
|
||||||
|
JsonView jsonView,
|
||||||
|
Type returnType) {
|
||||||
|
if (ArrayUtils.isEmpty(methodProduces)) {
|
||||||
|
return new Content();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果返回类型已经包含全局响应包装类,直接处理
|
||||||
|
if (isAlreadyWrapped(returnType)) {
|
||||||
|
return buildContentForWrappedType(components, annotations, methodProduces, jsonView, returnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包装返回类型为全局响应格式
|
||||||
|
Type wrappedType = wrapReturnType(returnType);
|
||||||
|
|
||||||
|
if (isVoid(wrappedType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildContentForWrappedType(components, annotations, methodProduces, jsonView, wrappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查返回类型是否已被全局响应包装
|
||||||
|
*
|
||||||
|
* @param returnType 返回类型
|
||||||
|
* @return 是否已被包装
|
||||||
|
*/
|
||||||
|
private boolean isAlreadyWrapped(Type returnType) {
|
||||||
|
return returnType.getTypeName().contains(globalResponseProperties.getResponseClassFullName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包装返回类型为全局响应格式
|
||||||
|
*
|
||||||
|
* @param returnType 原始返回类型
|
||||||
|
* @return 包装后的类型
|
||||||
|
*/
|
||||||
|
private Type wrapReturnType(Type returnType) {
|
||||||
|
if (returnType == void.class || returnType == Void.class) {
|
||||||
|
return TypeUtils.parameterize(responseClass, Void.class);
|
||||||
|
}
|
||||||
|
return TypeUtils.parameterize(responseClass, returnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为包装后的类型构建内容
|
||||||
|
*
|
||||||
|
* @param components 组件信息
|
||||||
|
* @param annotations 方法注解
|
||||||
|
* @param methodProduces 方法支持的媒体类型
|
||||||
|
* @param jsonView JSON 视图
|
||||||
|
* @param returnType 返回类型
|
||||||
|
* @return 响应内容
|
||||||
|
*/
|
||||||
|
private Content buildContentForWrappedType(Components components,
|
||||||
|
Annotation[] annotations,
|
||||||
|
String[] methodProduces,
|
||||||
|
JsonView jsonView,
|
||||||
|
Type returnType) {
|
||||||
|
Content content = new Content();
|
||||||
|
Schema<?> schema = calculateSchema(components, returnType, jsonView, annotations);
|
||||||
|
|
||||||
|
if (schema != null) {
|
||||||
|
io.swagger.v3.oas.models.media.MediaType mediaType = new io.swagger.v3.oas.models.media.MediaType();
|
||||||
|
mediaType.setSchema(schema);
|
||||||
|
setContent(methodProduces, content, mediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查类型是否为 void 类型
|
||||||
|
*
|
||||||
|
* @param returnType 返回类型
|
||||||
|
* @return 是否为 void 类型
|
||||||
|
*/
|
||||||
|
private boolean isVoid(Type returnType) {
|
||||||
|
if (Void.TYPE.equals(returnType) || Void.class.equals(returnType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnType instanceof ParameterizedType parameterizedType) {
|
||||||
|
Type[] types = parameterizedType.getActualTypeArguments();
|
||||||
|
if (isResponseTypeWrapper(ResolvableType.forType(returnType).getRawClass())) {
|
||||||
|
return isVoid(types[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算响应 Schema
|
||||||
|
*
|
||||||
|
* @param components 组件信息
|
||||||
|
* @param returnType 返回类型
|
||||||
|
* @param jsonView JSON 视图
|
||||||
|
* @param annotations 方法注解
|
||||||
|
* @return Schema 对象
|
||||||
|
*/
|
||||||
|
private Schema<?> calculateSchema(Components components,
|
||||||
|
Type returnType,
|
||||||
|
JsonView jsonView,
|
||||||
|
Annotation[] annotations) {
|
||||||
|
if (isVoid(returnType) || SpringDocAnnotationsUtils.isAnnotationToIgnore(returnType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return extractSchema(components, returnType, jsonView, annotations, propertyResolverUtils.getSpecVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置响应内容的媒体类型
|
||||||
|
*
|
||||||
|
* @param methodProduces 方法支持的媒体类型数组
|
||||||
|
* @param content 响应内容
|
||||||
|
* @param mediaType 媒体类型对象
|
||||||
|
*/
|
||||||
|
private void setContent(String[] methodProduces,
|
||||||
|
Content content,
|
||||||
|
io.swagger.v3.oas.models.media.MediaType mediaType) {
|
||||||
|
Arrays.stream(methodProduces).forEach(mediaTypeStr -> content.addMediaType(mediaTypeStr, mediaType));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.continew.admin.common.config.doc;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties;
|
||||||
|
import top.nextdoc4j.security.core.enhancer.NextDoc4jPathExcluder;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NextDoc4j 自定义路径过滤
|
||||||
|
*
|
||||||
|
* @author echo
|
||||||
|
* @since 2025/12/18
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NextDoc4jCustomPathFiltering implements NextDoc4jPathExcluder {
|
||||||
|
|
||||||
|
private final SaTokenExtensionProperties saTokenExtensionProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getExcludedPaths() {
|
||||||
|
Set<String> paths = new HashSet<>();
|
||||||
|
this.addConfiguredExcludes(paths);
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 Sa-Token 配置中的排除路径
|
||||||
|
*/
|
||||||
|
private void addConfiguredExcludes(Set<String> paths) {
|
||||||
|
if (saTokenExtensionProperties == null || saTokenExtensionProperties
|
||||||
|
.getSecurity() == null || saTokenExtensionProperties.getSecurity().getExcludes() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.addAll(Arrays.asList(saTokenExtensionProperties.getSecurity().getExcludes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
// 在 RequestMappingHandlerMapping Excluder 之后执行
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.continew.admin.common.config.doc;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.Operation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import top.continew.admin.common.base.controller.BaseController;
|
||||||
|
import top.continew.admin.common.config.crud.CrudApiPermissionPrefixCache;
|
||||||
|
import top.continew.starter.extension.crud.annotation.CrudApi;
|
||||||
|
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||||
|
import top.continew.starter.extension.crud.enums.Api;
|
||||||
|
import top.nextdoc4j.security.core.enhancer.NextDoc4jSecurityMetadataResolver;
|
||||||
|
import top.nextdoc4j.security.core.model.NextDoc4jSecurityMetadata;
|
||||||
|
import top.nextdoc4j.security.satoken.constant.NextDoc4jSaTokenConstant;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NextDoc4j 自定义权限码展示
|
||||||
|
*
|
||||||
|
* @author echo
|
||||||
|
* @since 2025/12/18
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NextDoc4jCustomPermissionDisplay implements NextDoc4jSecurityMetadataResolver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resolve(HandlerMethod handlerMethod, Operation operation, NextDoc4jSecurityMetadata metadata) {
|
||||||
|
// 处理 CrudRequestMapping 和 CrudApi 注解生成的权限信息
|
||||||
|
resolveCrudPermission(handlerMethod, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(HandlerMethod handlerMethod) {
|
||||||
|
// 检查类上是否有 CrudRequestMapping 注解且方法上有 CrudApi 注解
|
||||||
|
Class<?> targetClass = handlerMethod.getBeanType();
|
||||||
|
CrudRequestMapping crudRequestMapping = targetClass.getAnnotation(CrudRequestMapping.class);
|
||||||
|
CrudApi crudApi = handlerMethod.getMethodAnnotation(CrudApi.class);
|
||||||
|
return crudRequestMapping != null && crudApi != null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "CustomPermissionDisplay";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 CRUD 权限信息
|
||||||
|
*/
|
||||||
|
private void resolveCrudPermission(HandlerMethod handlerMethod, NextDoc4jSecurityMetadata metadata) {
|
||||||
|
Class<?> targetClass = handlerMethod.getBeanType();
|
||||||
|
CrudRequestMapping crudRequestMapping = targetClass.getAnnotation(CrudRequestMapping.class);
|
||||||
|
CrudApi crudApi = handlerMethod.getMethodAnnotation(CrudApi.class);
|
||||||
|
|
||||||
|
if (crudRequestMapping == null || crudApi == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查方法上是否有 @SaIgnore 注解,如果有则跳过
|
||||||
|
if (hasSaIgnore(handlerMethod)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查方法上是否已经有 @SaCheckRole 或 @SaCheckPermission 注解
|
||||||
|
// 如果有,重写了方法,跳过 CRUD 自动生成,让插件处理
|
||||||
|
if (hasSaTokenAnnotation(handlerMethod)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过字典类型的 API
|
||||||
|
if (Api.DICT.equals(crudApi.value()) || Api.TREE_DICT.equals(crudApi.value())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取权限前缀
|
||||||
|
String permissionPrefix = CrudApiPermissionPrefixCache.get(targetClass);
|
||||||
|
if (permissionPrefix == null || permissionPrefix.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 API 名称并生成权限字符串
|
||||||
|
String apiName = BaseController.getApiName(crudApi.value());
|
||||||
|
String permission = "%s:%s".formatted(permissionPrefix, apiName.toLowerCase());
|
||||||
|
|
||||||
|
// 添加到权限列表中
|
||||||
|
metadata.addPermission(new String[] {permission}, "AND", "crud", new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查方法或类上是否有 @SaIgnore 注解
|
||||||
|
*/
|
||||||
|
private boolean hasSaIgnore(HandlerMethod handlerMethod) {
|
||||||
|
// 检查方法上的 @SaIgnore 注解
|
||||||
|
Annotation methodAnnotation = handlerMethod.getMethodAnnotation(NextDoc4jSaTokenConstant.SA_IGNORE_CLASS);
|
||||||
|
if (methodAnnotation != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查类上的 @SaIgnore 注解
|
||||||
|
Annotation classAnnotation = handlerMethod.getBeanType()
|
||||||
|
.getAnnotation(NextDoc4jSaTokenConstant.SA_IGNORE_CLASS);
|
||||||
|
return classAnnotation != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查方法或类上是否有 @SaCheckRole 或 @SaCheckPermission 注解
|
||||||
|
* 如果有这些注解,说明开发者手动配置了权限,应该跳过 CRUD 自动生成
|
||||||
|
*/
|
||||||
|
private boolean hasSaTokenAnnotation(HandlerMethod handlerMethod) {
|
||||||
|
// 检查方法上的注解
|
||||||
|
Annotation methodPermission = handlerMethod
|
||||||
|
.getMethodAnnotation(NextDoc4jSaTokenConstant.SA_CHECK_PERMISSION_CLASS);
|
||||||
|
Annotation methodRole = handlerMethod.getMethodAnnotation(NextDoc4jSaTokenConstant.SA_CHECK_ROLE_CLASS);
|
||||||
|
|
||||||
|
if (methodPermission != null || methodRole != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查类上的注解
|
||||||
|
Class<?> beanType = handlerMethod.getBeanType();
|
||||||
|
Annotation classPermission = beanType.getAnnotation(NextDoc4jSaTokenConstant.SA_CHECK_PERMISSION_CLASS);
|
||||||
|
Annotation classRole = beanType.getAnnotation(NextDoc4jSaTokenConstant.SA_CHECK_ROLE_CLASS);
|
||||||
|
|
||||||
|
return classPermission != null || classRole != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package top.continew.admin.common.config.doc;
|
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
|
||||||
import cn.dev33.satoken.annotation.SaMode;
|
|
||||||
import org.springframework.web.method.HandlerMethod;
|
|
||||||
import top.continew.admin.common.base.controller.BaseController;
|
|
||||||
import top.continew.admin.common.config.crud.CrudApiPermissionPrefixCache;
|
|
||||||
import top.continew.starter.core.constant.StringConstants;
|
|
||||||
import top.continew.starter.extension.crud.annotation.CrudApi;
|
|
||||||
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
|
||||||
import top.continew.starter.extension.crud.enums.Api;
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Operation 描述定制器 处理 sa-token 鉴权标识符
|
|
||||||
*
|
|
||||||
* @author echo
|
|
||||||
* @since 2024/6/14 11:18
|
|
||||||
*/
|
|
||||||
public class OperationDescriptionCustomizer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 sa-token 注解信息
|
|
||||||
*
|
|
||||||
* @param handlerMethod 处理程序方法
|
|
||||||
* @return 包含权限和角色校验信息的列表
|
|
||||||
*/
|
|
||||||
public List<String> getPermission(HandlerMethod handlerMethod) {
|
|
||||||
List<String> values = new ArrayList<>();
|
|
||||||
|
|
||||||
// 获取权限校验信息
|
|
||||||
String permissionInfo = getAnnotationInfo(handlerMethod, SaCheckPermission.class, "权限校验:");
|
|
||||||
if (!permissionInfo.isEmpty()) {
|
|
||||||
values.add(permissionInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取角色校验信息
|
|
||||||
String roleInfo = getAnnotationInfo(handlerMethod, SaCheckRole.class, "角色校验:");
|
|
||||||
if (!roleInfo.isEmpty()) {
|
|
||||||
values.add(roleInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 CrudRequestMapping 和 CrudApi 注解生成的权限信息
|
|
||||||
String crudPermissionInfo = getCrudPermissionInfo(handlerMethod);
|
|
||||||
if (!crudPermissionInfo.isEmpty()) {
|
|
||||||
values.add(crudPermissionInfo);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取类和方法上指定注解的信息
|
|
||||||
*
|
|
||||||
* @param handlerMethod 处理程序方法
|
|
||||||
* @param annotationClass 注解类
|
|
||||||
* @param title 信息标题
|
|
||||||
* @param <A> 注解类型
|
|
||||||
* @return 拼接好的注解信息字符串
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <A extends Annotation> String getAnnotationInfo(HandlerMethod handlerMethod,
|
|
||||||
Class<A> annotationClass,
|
|
||||||
String title) {
|
|
||||||
StringBuilder infoBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
// 获取类上的注解
|
|
||||||
A classAnnotation = handlerMethod.getBeanType().getAnnotation(annotationClass);
|
|
||||||
if (classAnnotation != null) {
|
|
||||||
appendAnnotationInfo(infoBuilder, "类:", classAnnotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取方法上的注解
|
|
||||||
A methodAnnotation = handlerMethod.getMethodAnnotation(annotationClass);
|
|
||||||
if (methodAnnotation != null) {
|
|
||||||
appendAnnotationInfo(infoBuilder, "方法:", methodAnnotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有注解信息,添加标题
|
|
||||||
if (!infoBuilder.isEmpty()) {
|
|
||||||
infoBuilder.insert(0, "<font style=\"color:red\" class=\"light-red\">" + title + "</font></br>");
|
|
||||||
}
|
|
||||||
|
|
||||||
return infoBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拼接注解信息到 StringBuilder 中
|
|
||||||
*
|
|
||||||
* @param builder 用于拼接信息的 StringBuilder
|
|
||||||
* @param prefix 前缀信息,如 "类:" 或 "方法:"
|
|
||||||
* @param annotation 注解对象
|
|
||||||
*/
|
|
||||||
private void appendAnnotationInfo(StringBuilder builder, String prefix, Annotation annotation) {
|
|
||||||
String[] values = null;
|
|
||||||
SaMode mode = null;
|
|
||||||
String type = "";
|
|
||||||
String[] orRole = new String[0];
|
|
||||||
|
|
||||||
if (annotation instanceof SaCheckPermission checkPermission) {
|
|
||||||
values = checkPermission.value();
|
|
||||||
mode = checkPermission.mode();
|
|
||||||
type = checkPermission.type();
|
|
||||||
orRole = checkPermission.orRole();
|
|
||||||
} else if (annotation instanceof SaCheckRole checkRole) {
|
|
||||||
values = checkRole.value();
|
|
||||||
mode = checkRole.mode();
|
|
||||||
type = checkRole.type();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values != null && mode != null) {
|
|
||||||
builder.append("<font style=\"color:red\" class=\"light-red\">");
|
|
||||||
builder.append(prefix);
|
|
||||||
if (!type.isEmpty()) {
|
|
||||||
builder.append("(类型:").append(type).append(")");
|
|
||||||
}
|
|
||||||
builder.append(getAnnotationNote(values, mode));
|
|
||||||
if (orRole.length > 0) {
|
|
||||||
builder.append(" 或 角色校验(").append(getAnnotationNote(orRole, mode)).append(")");
|
|
||||||
}
|
|
||||||
builder.append("</font></br>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据注解的模式拼接注解值
|
|
||||||
*
|
|
||||||
* @param values 注解的值数组
|
|
||||||
* @param mode 注解的模式(AND 或 OR)
|
|
||||||
* @return 拼接好的注解值字符串
|
|
||||||
*/
|
|
||||||
private String getAnnotationNote(String[] values, SaMode mode) {
|
|
||||||
if (mode.equals(SaMode.AND)) {
|
|
||||||
return String.join(" 且 ", values);
|
|
||||||
} else {
|
|
||||||
return String.join(" 或 ", values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 CrudRequestMapping 和 CrudApi 注解生成的权限信息
|
|
||||||
*
|
|
||||||
* @param handlerMethod 处理程序方法
|
|
||||||
* @return 拼接好的权限信息字符串
|
|
||||||
* @see BaseController#preHandle(CrudApi, Object[], Method, Class)
|
|
||||||
*/
|
|
||||||
private String getCrudPermissionInfo(HandlerMethod handlerMethod) {
|
|
||||||
Class<?> targetClass = handlerMethod.getBeanType();
|
|
||||||
CrudRequestMapping crudRequestMapping = targetClass.getAnnotation(CrudRequestMapping.class);
|
|
||||||
CrudApi crudApi = handlerMethod.getMethodAnnotation(CrudApi.class);
|
|
||||||
if (crudRequestMapping == null || crudApi == null) {
|
|
||||||
return StringConstants.EMPTY;
|
|
||||||
}
|
|
||||||
if (Api.DICT.equals(crudApi.value()) || Api.TREE_DICT.equals(crudApi.value())) {
|
|
||||||
return StringConstants.EMPTY;
|
|
||||||
}
|
|
||||||
String permissionPrefix = CrudApiPermissionPrefixCache.get(targetClass);
|
|
||||||
String apiName = BaseController.getApiName(crudApi.value());
|
|
||||||
String permission = "%s:%s".formatted(permissionPrefix, apiName.toLowerCase());
|
|
||||||
return "<font style=\"color:red\" class=\"light-red\">CRUD 权限校验:</font></br><font style=\"color:red\" class=\"light-red\">方法:</font><font style=\"color:red\" class=\"light-red\">" + permission + "</font>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,6 +25,7 @@ import org.springframework.context.support.DefaultMessageSourceResolvable;
|
|||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||||
@@ -39,8 +40,6 @@ import top.continew.starter.core.exception.BaseException;
|
|||||||
import top.continew.starter.core.exception.BusinessException;
|
import top.continew.starter.core.exception.BusinessException;
|
||||||
import top.continew.starter.web.model.R;
|
import top.continew.starter.web.model.R;
|
||||||
|
|
||||||
import org.springframework.validation.BindException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局异常处理器
|
* 全局异常处理器
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- SnailJob 服务端 -->
|
<!-- SnailJob 服务端 -->
|
||||||
<snail-job.version>1.5.0</snail-job.version>
|
<snail-job.version>1.8.0</snail-job.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- liquibase formatted sql
|
-- liquibase formatted sql
|
||||||
|
|
||||||
-- changeset snail-job-server:1.5.0
|
-- changeset snail-job-server:1.8.0
|
||||||
-- 默认用户:admin/admin
|
-- 默认用户:admin/admin
|
||||||
INSERT INTO `sj_system_user` (username, password, role)
|
INSERT INTO `sj_system_user` (username, password, role)
|
||||||
VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
|
VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- liquibase formatted sql
|
-- liquibase formatted sql
|
||||||
|
|
||||||
-- changeset snail-job-server:1.5.0
|
-- changeset snail-job-server:1.8.0
|
||||||
SET NAMES utf8mb4;
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
CREATE TABLE `sj_namespace`
|
CREATE TABLE `sj_namespace`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- liquibase formatted sql
|
-- liquibase formatted sql
|
||||||
|
|
||||||
-- changeset snail-job-server:1.5.0
|
-- changeset snail-job-server:1.8.0
|
||||||
-- 默认用户:admin/admin
|
-- 默认用户:admin/admin
|
||||||
INSERT INTO sj_system_user (username, password, role)
|
INSERT INTO sj_system_user (username, password, role)
|
||||||
VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
|
VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- liquibase formatted sql
|
-- liquibase formatted sql
|
||||||
|
|
||||||
-- changeset snail-job-server:1.5.0
|
-- changeset snail-job-server:1.8.0
|
||||||
-- sj_namespace
|
-- sj_namespace
|
||||||
CREATE TABLE sj_namespace
|
CREATE TABLE sj_namespace
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
package top.continew.admin;
|
package top.continew.admin;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.net.NetUtil;
|
import cn.hutool.core.net.NetUtil;
|
||||||
import cn.hutool.core.util.URLUtil;
|
import cn.hutool.core.util.URLUtil;
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import com.alicp.jetcache.anno.config.EnableMethodCache;
|
import com.alicp.jetcache.anno.config.EnableMethodCache;
|
||||||
import com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties;
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -35,10 +35,12 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
|
|||||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import top.continew.starter.core.ContiNewStarterVersion;
|
||||||
import top.continew.starter.core.autoconfigure.application.ApplicationProperties;
|
import top.continew.starter.core.autoconfigure.application.ApplicationProperties;
|
||||||
import top.continew.starter.extension.crud.annotation.EnableCrudApi;
|
import top.continew.starter.extension.crud.annotation.EnableCrudApi;
|
||||||
import top.continew.starter.web.annotation.EnableGlobalResponse;
|
import top.continew.starter.web.annotation.EnableGlobalResponse;
|
||||||
import top.continew.starter.web.model.R;
|
import top.continew.starter.web.model.R;
|
||||||
|
import top.nextdoc4j.core.configuration.NextDoc4jProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动程序
|
* 启动程序
|
||||||
@@ -61,7 +63,9 @@ public class ContiNewAdminApplication implements ApplicationRunner {
|
|||||||
private final ServerProperties serverProperties;
|
private final ServerProperties serverProperties;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(ContiNewAdminApplication.class, args);
|
SpringApplication application = new SpringApplication(ContiNewAdminApplication.class);
|
||||||
|
application.setDefaultProperties(MapUtil.of("continew-starter.version", ContiNewStarterVersion.getVersion()));
|
||||||
|
application.run(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Hidden
|
@Hidden
|
||||||
@@ -79,13 +83,13 @@ public class ContiNewAdminApplication implements ApplicationRunner {
|
|||||||
String baseUrl = URLUtil.normalize("%s:%s%s".formatted(hostAddress, port, contextPath));
|
String baseUrl = URLUtil.normalize("%s:%s%s".formatted(hostAddress, port, contextPath));
|
||||||
log.info("--------------------------------------------------------");
|
log.info("--------------------------------------------------------");
|
||||||
log.info("{} server started successfully.", applicationProperties.getName());
|
log.info("{} server started successfully.", applicationProperties.getName());
|
||||||
log.info("ContiNew Starter: v{} (Spring Boot: v{})", SpringUtil
|
log.info("ContiNew Starter: v{} (Spring Boot: v{})", ContiNewStarterVersion.getVersion(), SpringBootVersion
|
||||||
.getProperty("application.starter"), SpringBootVersion.getVersion());
|
.getVersion());
|
||||||
log.info("当前版本: v{} (Profile: {})", applicationProperties.getVersion(), SpringUtil
|
log.info("当前版本: v{} (Profile: {})", applicationProperties.getVersion(), SpringUtil
|
||||||
.getProperty("spring.profiles.active"));
|
.getProperty("spring.profiles.active"));
|
||||||
log.info("服务地址: {}", baseUrl);
|
log.info("服务地址: {}", baseUrl);
|
||||||
Knife4jProperties knife4jProperties = SpringUtil.getBean(Knife4jProperties.class);
|
NextDoc4jProperties docProperties = SpringUtil.getBean(NextDoc4jProperties.class);
|
||||||
if (!knife4jProperties.isProduction()) {
|
if (!docProperties.isProduction()) {
|
||||||
log.info("接口文档: {}/doc.html", baseUrl);
|
log.info("接口文档: {}/doc.html", baseUrl);
|
||||||
}
|
}
|
||||||
log.info("吐槽广场: https://continew.top/docs/admin/issue-hub.html");
|
log.info("吐槽广场: https://continew.top/docs/admin/issue-hub.html");
|
||||||
@@ -94,4 +98,4 @@ public class ContiNewAdminApplication implements ApplicationRunner {
|
|||||||
log.info("ContiNew Admin: 持续迭代优化的,高质量多租户中后台管理系统框架");
|
log.info("ContiNew Admin: 持续迭代优化的,高质量多租户中后台管理系统框架");
|
||||||
log.info("--------------------------------------------------------");
|
log.info("--------------------------------------------------------");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
\____|\___/ |_| |_| \__||_||_| \_| \___| \_/\_/ /_/ \_\\__,_||_| |_| |_||_||_| |_|
|
\____|\___/ |_| |_| \__||_||_| \_| \___| \_/\_/ /_/ \_\\__,_||_| |_| |_||_||_| |_|
|
||||||
|
|
||||||
:: ${application.name} :: v${application.version} (Profile: ${spring.profiles.active})
|
:: ${application.name} :: v${application.version} (Profile: ${spring.profiles.active})
|
||||||
:: ContiNew Starter :: v${application.starter}
|
:: ContiNew Starter :: v${continew-starter.version}
|
||||||
:: Spring Boot :: v${spring-boot.version}
|
:: Spring Boot :: v${spring-boot.version}
|
||||||
|
|||||||
@@ -213,29 +213,8 @@ continew-starter.messaging.websocket:
|
|||||||
# 配置允许跨域的域名
|
# 配置允许跨域的域名
|
||||||
allowed-origins: '*'
|
allowed-origins: '*'
|
||||||
|
|
||||||
--- ### Sa-Token 扩展配置
|
|
||||||
sa-token.extension:
|
|
||||||
# 安全配置:排除(放行)路径配置
|
|
||||||
security.excludes:
|
|
||||||
- /error
|
|
||||||
# 静态资源
|
|
||||||
- /*.html
|
|
||||||
- /*/*.html
|
|
||||||
- /*/*.css
|
|
||||||
- /*/*.js
|
|
||||||
- /websocket/**
|
|
||||||
# 接口文档相关资源
|
|
||||||
- /favicon.ico
|
|
||||||
- /doc.html
|
|
||||||
- /webjars/**
|
|
||||||
- /swagger-ui/**
|
|
||||||
- /swagger-resources/**
|
|
||||||
- /*/api-docs/**
|
|
||||||
# 本地存储资源
|
|
||||||
- /file/**
|
|
||||||
|
|
||||||
--- ### Just Auth 配置
|
--- ### Just Auth 配置
|
||||||
justauth:
|
continew-starter.justauth:
|
||||||
enabled: true
|
enabled: true
|
||||||
type:
|
type:
|
||||||
WECHAT_OPEN:
|
WECHAT_OPEN:
|
||||||
@@ -253,6 +232,28 @@ justauth:
|
|||||||
cache:
|
cache:
|
||||||
type: REDIS
|
type: REDIS
|
||||||
|
|
||||||
|
--- ### Sa-Token 扩展配置
|
||||||
|
sa-token.extension:
|
||||||
|
# 安全配置:排除(放行)路径配置
|
||||||
|
security.excludes:
|
||||||
|
- /error
|
||||||
|
# 静态资源
|
||||||
|
- /*.html
|
||||||
|
- /*/*.html
|
||||||
|
- /*/*.css
|
||||||
|
- /*/*.js
|
||||||
|
- /websocket/**
|
||||||
|
# 接口文档相关资源
|
||||||
|
- /favicon.ico
|
||||||
|
- /doc.html
|
||||||
|
- /nextdoc/**
|
||||||
|
- /webjars/**
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /swagger-resources/**
|
||||||
|
- /*/api-docs/**
|
||||||
|
# 本地存储资源
|
||||||
|
- /file/**
|
||||||
|
|
||||||
--- ### Snail Job 配置
|
--- ### Snail Job 配置
|
||||||
snail-job:
|
snail-job:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ jetcache:
|
|||||||
|
|
||||||
--- ### 接口文档配置
|
--- ### 接口文档配置
|
||||||
## 接口文档增强配置
|
## 接口文档增强配置
|
||||||
knife4j:
|
nextdoc4j:
|
||||||
# 开启生产环境屏蔽
|
# 开启生产环境屏蔽
|
||||||
production: ${application.production}
|
production: ${application.production}
|
||||||
|
|
||||||
@@ -222,22 +222,8 @@ continew-starter.messaging.websocket:
|
|||||||
allowed-origins:
|
allowed-origins:
|
||||||
- ${application.url}
|
- ${application.url}
|
||||||
|
|
||||||
--- ### Sa-Token 扩展配置
|
|
||||||
sa-token.extension:
|
|
||||||
# 安全配置:排除(放行)路径配置
|
|
||||||
security.excludes:
|
|
||||||
- /error
|
|
||||||
# 静态资源
|
|
||||||
- /*.html
|
|
||||||
- /*/*.html
|
|
||||||
- /*/*.css
|
|
||||||
- /*/*.js
|
|
||||||
- /websocket/**
|
|
||||||
# 本地存储资源
|
|
||||||
- /file/**
|
|
||||||
|
|
||||||
--- ### Just Auth 配置
|
--- ### Just Auth 配置
|
||||||
justauth:
|
continew-starter.justauth:
|
||||||
enabled: true
|
enabled: true
|
||||||
type:
|
type:
|
||||||
WECHAT_OPEN:
|
WECHAT_OPEN:
|
||||||
@@ -255,6 +241,20 @@ justauth:
|
|||||||
cache:
|
cache:
|
||||||
type: REDIS
|
type: REDIS
|
||||||
|
|
||||||
|
--- ### Sa-Token 扩展配置
|
||||||
|
sa-token.extension:
|
||||||
|
# 安全配置:排除(放行)路径配置
|
||||||
|
security.excludes:
|
||||||
|
- /error
|
||||||
|
# 静态资源
|
||||||
|
- /*.html
|
||||||
|
- /*/*.html
|
||||||
|
- /*/*.css
|
||||||
|
- /*/*.js
|
||||||
|
- /websocket/**
|
||||||
|
# 本地存储资源
|
||||||
|
- /file/**
|
||||||
|
|
||||||
--- ### Snail Job 配置
|
--- ### Snail Job 配置
|
||||||
snail-job:
|
snail-job:
|
||||||
# 客户端地址(默认自动获取本机 IP)
|
# 客户端地址(默认自动获取本机 IP)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ application:
|
|||||||
description: 持续迭代优化的前后端分离中后台管理系统框架,开箱即用,持续提供舒适的开发体验。
|
description: 持续迭代优化的前后端分离中后台管理系统框架,开箱即用,持续提供舒适的开发体验。
|
||||||
# 版本
|
# 版本
|
||||||
version: 4.2.0-SNAPSHOT
|
version: 4.2.0-SNAPSHOT
|
||||||
starter: 2.14.0
|
|
||||||
# 基本包
|
# 基本包
|
||||||
base-package: top.continew.admin
|
base-package: top.continew.admin
|
||||||
## 作者信息配置
|
## 作者信息配置
|
||||||
@@ -123,15 +122,29 @@ springdoc:
|
|||||||
name: ${sa-token.token-name}
|
name: ${sa-token.token-name}
|
||||||
scheme: ${sa-token.token-prefix}
|
scheme: ${sa-token.token-prefix}
|
||||||
## 接口文档增强配置
|
## 接口文档增强配置
|
||||||
knife4j:
|
nextdoc4j:
|
||||||
enable: true
|
# 启用开关
|
||||||
setting:
|
enabled: true
|
||||||
# 是否显示默认的 footer(默认 true,显示)
|
# 扩展配置
|
||||||
enable-footer: false
|
extension:
|
||||||
# 是否自定义 footer(默认 false,非自定义)
|
# 扩展开关
|
||||||
enable-footer-custom: true
|
enabled: true
|
||||||
# 自定义 footer 内容,支持 Markdown 语法
|
# 品牌配置
|
||||||
footer-custom-content: 'Copyright © 2022-present [${application.contact.name}](${application.contact.url}) ⋅ [${application.name}](${application.url}) v${application.version}'
|
brand:
|
||||||
|
# logo 展示
|
||||||
|
logo: classpath:favicon.ico
|
||||||
|
# 标题
|
||||||
|
title: ${application.name}
|
||||||
|
# 自定义 footer 内容
|
||||||
|
footer-text: 'Copyright © 2022-present [${application.contact.name}](${application.contact.url}) ⋅ [${application.name}](${application.url}) v${application.version}'
|
||||||
|
# 插件配置
|
||||||
|
plugin:
|
||||||
|
# 枚举展示开关
|
||||||
|
enum:
|
||||||
|
enabled: true
|
||||||
|
# 认证展示和 sa-token 权限码展示开关
|
||||||
|
security:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
--- ### 全局响应配置
|
--- ### 全局响应配置
|
||||||
continew-starter.web.response:
|
continew-starter.web.response:
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package top.continew.admin.auth.controller;
|
|||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
@@ -27,10 +26,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import me.zhyd.oauth.AuthRequestBuilder;
|
|
||||||
import me.zhyd.oauth.config.AuthConfig;
|
|
||||||
import me.zhyd.oauth.request.AuthRequest;
|
import me.zhyd.oauth.request.AuthRequest;
|
||||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import top.continew.admin.auth.model.req.LoginReq;
|
import top.continew.admin.auth.model.req.LoginReq;
|
||||||
import top.continew.admin.auth.model.resp.LoginResp;
|
import top.continew.admin.auth.model.resp.LoginResp;
|
||||||
@@ -40,10 +38,12 @@ import top.continew.admin.auth.model.resp.UserInfoResp;
|
|||||||
import top.continew.admin.auth.service.AuthService;
|
import top.continew.admin.auth.service.AuthService;
|
||||||
import top.continew.admin.common.context.UserContext;
|
import top.continew.admin.common.context.UserContext;
|
||||||
import top.continew.admin.common.context.UserContextHolder;
|
import top.continew.admin.common.context.UserContextHolder;
|
||||||
|
import top.continew.admin.system.enums.SocialSourceEnum;
|
||||||
import top.continew.admin.system.model.resp.user.UserDetailResp;
|
import top.continew.admin.system.model.resp.user.UserDetailResp;
|
||||||
import top.continew.admin.system.service.UserService;
|
import top.continew.admin.system.service.UserService;
|
||||||
import top.continew.starter.core.exception.BadRequestException;
|
import top.continew.starter.auth.justauth.AuthRequestFactory;
|
||||||
import top.continew.starter.log.annotation.Log;
|
import top.continew.starter.log.annotation.Log;
|
||||||
|
import top.continew.starter.validation.constraints.EnumValue;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Tag(name = "认证 API")
|
@Tag(name = "认证 API")
|
||||||
@Log(module = "登录")
|
@Log(module = "登录")
|
||||||
|
@Validated
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
@@ -62,7 +63,7 @@ public class AuthController {
|
|||||||
|
|
||||||
private final AuthService authService;
|
private final AuthService authService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final JustAuthProperties authProperties;
|
private final AuthRequestFactory authRequestFactory;
|
||||||
|
|
||||||
@SaIgnore
|
@SaIgnore
|
||||||
@Operation(summary = "登录", description = "用户登录")
|
@Operation(summary = "登录", description = "用户登录")
|
||||||
@@ -84,8 +85,8 @@ public class AuthController {
|
|||||||
@Operation(summary = "三方账号登录授权", description = "三方账号登录授权")
|
@Operation(summary = "三方账号登录授权", description = "三方账号登录授权")
|
||||||
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
|
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
|
||||||
@GetMapping("/{source}")
|
@GetMapping("/{source}")
|
||||||
public SocialAuthAuthorizeResp authorize(@PathVariable String source) {
|
public SocialAuthAuthorizeResp authorize(@PathVariable @EnumValue(value = SocialSourceEnum.class, message = "第三方平台无效") String source) {
|
||||||
AuthRequest authRequest = this.getAuthRequest(source);
|
AuthRequest authRequest = authRequestFactory.getAuthRequest(source);
|
||||||
return SocialAuthAuthorizeResp.builder()
|
return SocialAuthAuthorizeResp.builder()
|
||||||
.authorizeUrl(authRequest.authorize(AuthStateUtils.createState()))
|
.authorizeUrl(authRequest.authorize(AuthStateUtils.createState()))
|
||||||
.build();
|
.build();
|
||||||
@@ -110,13 +111,4 @@ public class AuthController {
|
|||||||
public List<RouteResp> listRoute() {
|
public List<RouteResp> listRoute() {
|
||||||
return authService.buildRouteTree(UserContextHolder.getUserId());
|
return authService.buildRouteTree(UserContextHolder.getUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthRequest getAuthRequest(String source) {
|
|
||||||
try {
|
|
||||||
AuthConfig authConfig = authProperties.getType().get(source.toUpperCase());
|
|
||||||
return AuthRequestBuilder.builder().source(source).authConfig(authConfig).build();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,8 @@ import cn.hutool.core.util.RandomUtil;
|
|||||||
import cn.hutool.core.util.ReUtil;
|
import cn.hutool.core.util.ReUtil;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import me.zhyd.oauth.AuthRequestBuilder;
|
|
||||||
import me.zhyd.oauth.config.AuthConfig;
|
|
||||||
import me.zhyd.oauth.model.AuthCallback;
|
import me.zhyd.oauth.model.AuthCallback;
|
||||||
import me.zhyd.oauth.model.AuthResponse;
|
import me.zhyd.oauth.model.AuthResponse;
|
||||||
import me.zhyd.oauth.model.AuthUser;
|
import me.zhyd.oauth.model.AuthUser;
|
||||||
@@ -54,8 +51,8 @@ import top.continew.admin.system.service.DeptService;
|
|||||||
import top.continew.admin.system.service.MessageService;
|
import top.continew.admin.system.service.MessageService;
|
||||||
import top.continew.admin.system.service.UserRoleService;
|
import top.continew.admin.system.service.UserRoleService;
|
||||||
import top.continew.admin.system.service.UserSocialService;
|
import top.continew.admin.system.service.UserSocialService;
|
||||||
|
import top.continew.starter.auth.justauth.AuthRequestFactory;
|
||||||
import top.continew.starter.core.autoconfigure.application.ApplicationProperties;
|
import top.continew.starter.core.autoconfigure.application.ApplicationProperties;
|
||||||
import top.continew.starter.core.exception.BadRequestException;
|
|
||||||
import top.continew.starter.core.util.validation.ValidationUtils;
|
import top.continew.starter.core.util.validation.ValidationUtils;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -72,7 +69,7 @@ import java.util.Collections;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
|
public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
|
||||||
|
|
||||||
private final JustAuthProperties authProperties;
|
private final AuthRequestFactory authRequestFactory;
|
||||||
private final UserSocialService userSocialService;
|
private final UserSocialService userSocialService;
|
||||||
private final UserRoleService userRoleService;
|
private final UserRoleService userRoleService;
|
||||||
private final MessageService messageService;
|
private final MessageService messageService;
|
||||||
@@ -83,7 +80,7 @@ public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public LoginResp login(SocialLoginReq req, ClientResp client, HttpServletRequest request) {
|
public LoginResp login(SocialLoginReq req, ClientResp client, HttpServletRequest request) {
|
||||||
// 获取第三方登录信息
|
// 获取第三方登录信息
|
||||||
AuthRequest authRequest = this.getAuthRequest(req.getSource());
|
AuthRequest authRequest = authRequestFactory.getAuthRequest(req.getSource());
|
||||||
AuthCallback callback = new AuthCallback();
|
AuthCallback callback = new AuthCallback();
|
||||||
callback.setCode(req.getCode());
|
callback.setCode(req.getCode());
|
||||||
callback.setState(req.getState());
|
callback.setState(req.getState());
|
||||||
@@ -153,21 +150,6 @@ public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
|
|||||||
return AuthTypeEnum.SOCIAL;
|
return AuthTypeEnum.SOCIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 AuthRequest
|
|
||||||
*
|
|
||||||
* @param source 平台名称
|
|
||||||
* @return AuthRequest
|
|
||||||
*/
|
|
||||||
private AuthRequest getAuthRequest(String source) {
|
|
||||||
try {
|
|
||||||
AuthConfig authConfig = authProperties.getType().get(source.toUpperCase());
|
|
||||||
return AuthRequestBuilder.builder().source(source).authConfig(authConfig).build();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送安全消息
|
* 发送安全消息
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package top.continew.admin.system.controller;
|
package top.continew.admin.system.controller;
|
||||||
|
|
||||||
import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
@@ -24,8 +23,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import me.zhyd.oauth.AuthRequestBuilder;
|
|
||||||
import me.zhyd.oauth.config.AuthConfig;
|
|
||||||
import me.zhyd.oauth.model.AuthCallback;
|
import me.zhyd.oauth.model.AuthCallback;
|
||||||
import me.zhyd.oauth.model.AuthResponse;
|
import me.zhyd.oauth.model.AuthResponse;
|
||||||
import me.zhyd.oauth.model.AuthUser;
|
import me.zhyd.oauth.model.AuthUser;
|
||||||
@@ -46,10 +43,11 @@ import top.continew.admin.system.model.resp.AvatarResp;
|
|||||||
import top.continew.admin.system.model.resp.user.UserSocialBindResp;
|
import top.continew.admin.system.model.resp.user.UserSocialBindResp;
|
||||||
import top.continew.admin.system.service.UserService;
|
import top.continew.admin.system.service.UserService;
|
||||||
import top.continew.admin.system.service.UserSocialService;
|
import top.continew.admin.system.service.UserSocialService;
|
||||||
|
import top.continew.starter.auth.justauth.AuthRequestFactory;
|
||||||
import top.continew.starter.cache.redisson.util.RedisUtils;
|
import top.continew.starter.cache.redisson.util.RedisUtils;
|
||||||
import top.continew.starter.core.exception.BadRequestException;
|
|
||||||
import top.continew.starter.core.util.CollUtils;
|
import top.continew.starter.core.util.CollUtils;
|
||||||
import top.continew.starter.core.util.validation.ValidationUtils;
|
import top.continew.starter.core.util.validation.ValidationUtils;
|
||||||
|
import top.continew.starter.validation.constraints.EnumValue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -71,7 +69,7 @@ public class UserProfileController {
|
|||||||
private static final String CAPTCHA_EXPIRED = "验证码已失效";
|
private static final String CAPTCHA_EXPIRED = "验证码已失效";
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final UserSocialService userSocialService;
|
private final UserSocialService userSocialService;
|
||||||
private final JustAuthProperties authProperties;
|
private final AuthRequestFactory authRequestFactory;
|
||||||
|
|
||||||
@Operation(summary = "修改头像", description = "用户修改个人头像")
|
@Operation(summary = "修改头像", description = "用户修改个人头像")
|
||||||
@PatchMapping("/avatar")
|
@PatchMapping("/avatar")
|
||||||
@@ -135,8 +133,9 @@ public class UserProfileController {
|
|||||||
@Operation(summary = "绑定三方账号", description = "绑定三方账号")
|
@Operation(summary = "绑定三方账号", description = "绑定三方账号")
|
||||||
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
|
@Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH)
|
||||||
@PostMapping("/social/{source}")
|
@PostMapping("/social/{source}")
|
||||||
public void bindSocial(@PathVariable String source, @RequestBody AuthCallback callback) {
|
public void bindSocial(@PathVariable @EnumValue(value = SocialSourceEnum.class, message = "第三方平台无效") String source,
|
||||||
AuthRequest authRequest = this.getAuthRequest(source);
|
@RequestBody AuthCallback callback) {
|
||||||
|
AuthRequest authRequest = authRequestFactory.getAuthRequest(source);
|
||||||
AuthResponse<AuthUser> response = authRequest.login(callback);
|
AuthResponse<AuthUser> response = authRequest.login(callback);
|
||||||
ValidationUtils.throwIf(!response.ok(), response.getMsg());
|
ValidationUtils.throwIf(!response.ok(), response.getMsg());
|
||||||
AuthUser authUser = response.getData();
|
AuthUser authUser = response.getData();
|
||||||
@@ -149,13 +148,4 @@ public class UserProfileController {
|
|||||||
public void unbindSocial(@PathVariable String source) {
|
public void unbindSocial(@PathVariable String source) {
|
||||||
userSocialService.deleteBySourceAndUserId(source, UserContextHolder.getUserId());
|
userSocialService.deleteBySourceAndUserId(source, UserContextHolder.getUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthRequest getAuthRequest(String source) {
|
|
||||||
try {
|
|
||||||
AuthConfig authConfig = authProperties.getType().get(source.toUpperCase());
|
|
||||||
return AuthRequestBuilder.builder().source(source).authConfig(authConfig).build();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -13,7 +13,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>top.continew.starter</groupId>
|
<groupId>top.continew.starter</groupId>
|
||||||
<artifactId>continew-starter</artifactId>
|
<artifactId>continew-starter</artifactId>
|
||||||
<version>2.14.0</version>
|
<version>2.15.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>top.continew.admin</groupId>
|
<groupId>top.continew.admin</groupId>
|
||||||
|
|||||||
Reference in New Issue
Block a user