Commit 2bd2cb56a16da28d97054efad0abb3d58cd4ea76
Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/1.8.0-uniapp
Conflicts: yudao-module-mall/yudao-module-trade-biz/pom.xml
Showing
180 changed files
with
3840 additions
and
1011 deletions
Too many changes.
To preserve performance only 100 of 180 files are displayed.
README.md
| @@ -23,6 +23,8 @@ | @@ -23,6 +23,8 @@ | ||
| 23 | > | 23 | > |
| 24 | > 😜 给项目点点 Star 吧,这对我们真的很重要! | 24 | > 😜 给项目点点 Star 吧,这对我们真的很重要! |
| 25 | 25 | ||
| 26 | + | ||
| 27 | + | ||
| 26 | * 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) | 28 | * 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) |
| 27 | * 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5! | 29 | * 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5! |
| 28 | * 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson | 30 | * 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson |
| @@ -42,16 +44,19 @@ | @@ -42,16 +44,19 @@ | ||
| 42 | | `yudao-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)** [Github](https://github.com/YunaiV/yudao-cloud) | | 44 | | `yudao-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)** [Github](https://github.com/YunaiV/yudao-cloud) | |
| 43 | | `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)** [Github](https://github.com/YunaiV/SpringBoot-Labs) | | 45 | | `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)** [Github](https://github.com/YunaiV/SpringBoot-Labs) | |
| 44 | 46 | ||
| 45 | - | ||
| 46 | ## 🐼 内置功能 | 47 | ## 🐼 内置功能 |
| 47 | 48 | ||
| 48 | -分成多种内置功能: | 49 | +系统内置多种多种业务功能,可以用于快速你的业务系统: |
| 50 | + | ||
| 51 | + | ||
| 52 | + | ||
| 49 | * 系统功能 | 53 | * 系统功能 |
| 50 | * 基础设施 | 54 | * 基础设施 |
| 51 | * 工作流程 | 55 | * 工作流程 |
| 52 | * 支付系统 | 56 | * 支付系统 |
| 53 | -* 商城系统 | 57 | +* 会员中心 |
| 54 | * 数据报表 | 58 | * 数据报表 |
| 59 | +* 商城系统 | ||
| 55 | 60 | ||
| 56 | > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。 | 61 | > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。 |
| 57 | > | 62 | > |
| @@ -169,29 +174,29 @@ ps:核心功能已经实现,正在对接微信小程序中... | @@ -169,29 +174,29 @@ ps:核心功能已经实现,正在对接微信小程序中... | ||
| 169 | 174 | ||
| 170 | ### 后端 | 175 | ### 后端 |
| 171 | 176 | ||
| 172 | -| 框架 | 说明 | 版本 | 学习指南 | | ||
| 173 | -|---------------------------------------------------------------------------------------------|-----------------------|---------|----------------------------------------------------------------| | ||
| 174 | -| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.10 | [文档](https://github.com/YunaiV/SpringBoot-Labs) | | ||
| 175 | -| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | | | ||
| 176 | -| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.11 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | | ||
| 177 | -| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | | ||
| 178 | -| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | | ||
| 179 | -| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | | | ||
| 180 | -| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.4 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) | | ||
| 181 | -| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) | | ||
| 182 | -| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) | | ||
| 183 | -| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) | | ||
| 184 | -| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.2 | [文档](https://doc.iocoder.cn/bpm/) | | ||
| 185 | -| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) | | ||
| 186 | -| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) | | ||
| 187 | -| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) | | ||
| 188 | -| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) | | ||
| 189 | -| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.6.7 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) | | ||
| 190 | -| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | | | ||
| 191 | -| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) | | ||
| 192 | -| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) | | ||
| 193 | -| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - | | ||
| 194 | -| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - | | 177 | +| 框架 | 说明 | 版本 | 学习指南 | |
| 178 | +|---------------------------------------------------------------------------------------------|-----------------------|------------------|----------------------------------------------------------------| | ||
| 179 | +| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.12 | [文档](https://github.com/YunaiV/SpringBoot-Labs) | | ||
| 180 | +| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | | | ||
| 181 | +| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.13-SNSAPSHOT | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | | ||
| 182 | +| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | | ||
| 183 | +| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | | ||
| 184 | +| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | | | ||
| 185 | +| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.7 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) | | ||
| 186 | +| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) | | ||
| 187 | +| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) | | ||
| 188 | +| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) | | ||
| 189 | +| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.2 | [文档](https://doc.iocoder.cn/bpm/) | | ||
| 190 | +| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) | | ||
| 191 | +| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) | | ||
| 192 | +| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) | | ||
| 193 | +| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) | | ||
| 194 | +| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.6.9 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) | | ||
| 195 | +| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | | | ||
| 196 | +| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.5.2.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) | | ||
| 197 | +| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.24 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) | | ||
| 198 | +| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - | | ||
| 199 | +| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.8.0 | - | | ||
| 195 | 200 | ||
| 196 | ### [管理后台 Vue2 前端](./yudao-ui-admin) | 201 | ### [管理后台 Vue2 前端](./yudao-ui-admin) |
| 197 | 202 | ||
| @@ -204,14 +209,14 @@ ps:核心功能已经实现,正在对接微信小程序中... | @@ -204,14 +209,14 @@ ps:核心功能已经实现,正在对接微信小程序中... | ||
| 204 | 209 | ||
| 205 | | 框架 | 说明 | 版本 | | 210 | | 框架 | 说明 | 版本 | |
| 206 | |----------------------------------------------------------------------|-----------------|--------| | 211 | |----------------------------------------------------------------------|-----------------|--------| |
| 207 | -| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.37 | | ||
| 208 | -| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.4 | | ||
| 209 | -| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.12 | | ||
| 210 | -| [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.7.4 | | ||
| 211 | -| [pinia](https://pinia.vuejs.org/) | vuex5 | 2.0.17 | | 212 | +| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.41 | |
| 213 | +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.1.8 | | ||
| 214 | +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.18 | | ||
| 215 | +| [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.8.4 | | ||
| 216 | +| [pinia](https://pinia.vuejs.org/) | vuex5 | 2.0.23 | | ||
| 212 | | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 | | 217 | | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 | |
| 213 | | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | | 218 | | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | |
| 214 | -| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | | 219 | +| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.0.0 | |
| 215 | 220 | ||
| 216 | ### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp) | 221 | ### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp) |
| 217 | 222 |
pom.xml
| @@ -12,7 +12,7 @@ | @@ -12,7 +12,7 @@ | ||
| 12 | <module>yudao-framework</module> | 12 | <module>yudao-framework</module> |
| 13 | <!-- Server 主项目 --> | 13 | <!-- Server 主项目 --> |
| 14 | <module>yudao-server</module> | 14 | <module>yudao-server</module> |
| 15 | - <!-- 各种 module 拓展 --> | 15 | + <!-- 各种 module 拓展 --> |
| 16 | <module>yudao-module-member</module> | 16 | <module>yudao-module-member</module> |
| 17 | <module>yudao-module-bpm</module> | 17 | <module>yudao-module-bpm</module> |
| 18 | <module>yudao-module-system</module> | 18 | <module>yudao-module-system</module> |
| @@ -20,6 +20,8 @@ | @@ -20,6 +20,8 @@ | ||
| 20 | <module>yudao-module-pay</module> | 20 | <module>yudao-module-pay</module> |
| 21 | <module>yudao-module-mall</module> | 21 | <module>yudao-module-mall</module> |
| 22 | <module>yudao-module-visualization</module> | 22 | <module>yudao-module-visualization</module> |
| 23 | + <!-- 示例项目 --> | ||
| 24 | + <module>yudao-example</module> | ||
| 23 | </modules> | 25 | </modules> |
| 24 | 26 | ||
| 25 | <name>${project.artifactId}</name> | 27 | <name>${project.artifactId}</name> |
| @@ -35,8 +37,8 @@ | @@ -35,8 +37,8 @@ | ||
| 35 | <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> | 37 | <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> |
| 36 | <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version> | 38 | <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version> |
| 37 | <!-- 看看咋放到 bom 里 --> | 39 | <!-- 看看咋放到 bom 里 --> |
| 38 | - <lombok.version>1.18.20</lombok.version> | ||
| 39 | - <mapstruct.version>1.4.1.Final</mapstruct.version> | 40 | + <lombok.version>1.18.24</lombok.version> |
| 41 | + <mapstruct.version>1.5.2.Final</mapstruct.version> | ||
| 40 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 42 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| 41 | </properties> | 43 | </properties> |
| 42 | 44 |
yudao-dependencies/pom.xml
| @@ -16,58 +16,58 @@ | @@ -16,58 +16,58 @@ | ||
| 16 | <properties> | 16 | <properties> |
| 17 | <revision>1.6.4-snapshot</revision> | 17 | <revision>1.6.4-snapshot</revision> |
| 18 | <!-- 统一依赖管理 --> | 18 | <!-- 统一依赖管理 --> |
| 19 | - <spring.boot.version>2.6.10</spring.boot.version> | 19 | + <spring.boot.version>2.6.12</spring.boot.version> |
| 20 | <!-- Web 相关 --> | 20 | <!-- Web 相关 --> |
| 21 | <knife4j.version>3.0.3</knife4j.version> | 21 | <knife4j.version>3.0.3</knife4j.version> |
| 22 | - <swagger-annotations.version>1.6.6</swagger-annotations.version> | 22 | + <swagger-annotations.version>1.6.7</swagger-annotations.version> |
| 23 | <servlet.versoin>2.5</servlet.versoin> | 23 | <servlet.versoin>2.5</servlet.versoin> |
| 24 | <!-- DB 相关 --> | 24 | <!-- DB 相关 --> |
| 25 | - <druid.version>1.2.11</druid.version> | 25 | + <druid.version>1.2.13-SNSAPSHOT</druid.version> |
| 26 | <mybatis-plus.version>3.5.2</mybatis-plus.version> | 26 | <mybatis-plus.version>3.5.2</mybatis-plus.version> |
| 27 | <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version> | 27 | <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version> |
| 28 | - <dynamic-datasource.version>3.5.0</dynamic-datasource.version> | ||
| 29 | - <redisson.version>3.17.4</redisson.version> | 28 | + <dynamic-datasource.version>3.5.2</dynamic-datasource.version> |
| 29 | + <redisson.version>3.17.7</redisson.version> | ||
| 30 | <!-- Config 配置中心相关 --> | 30 | <!-- Config 配置中心相关 --> |
| 31 | - <apollo.version>1.9.2</apollo.version> | 31 | + <apollo.version>2.0.1</apollo.version> |
| 32 | <!-- 服务保障相关 --> | 32 | <!-- 服务保障相关 --> |
| 33 | - <lock4j.version>2.2.0</lock4j.version> | 33 | + <lock4j.version>2.2.2</lock4j.version> |
| 34 | <resilience4j.version>1.7.1</resilience4j.version> | 34 | <resilience4j.version>1.7.1</resilience4j.version> |
| 35 | <!-- 监控相关 --> | 35 | <!-- 监控相关 --> |
| 36 | - <skywalking.version>8.7.0</skywalking.version> | ||
| 37 | - <spring-boot-admin.version>2.6.7</spring-boot-admin.version> | ||
| 38 | - <opentracing.version>0.31.0</opentracing.version> | 36 | + <skywalking.version>8.12.0</skywalking.version> |
| 37 | + <spring-boot-admin.version>2.6.9</spring-boot-admin.version> | ||
| 38 | + <opentracing.version>0.33.0</opentracing.version> | ||
| 39 | <!-- Test 测试相关 --> | 39 | <!-- Test 测试相关 --> |
| 40 | - <podam.version>7.2.6.RELEASE</podam.version> | ||
| 41 | - <jedis-mock.version>0.1.16</jedis-mock.version> | ||
| 42 | - <mockito-inline.version>4.0.0</mockito-inline.version> | 40 | + <podam.version>7.2.9.RELEASE</podam.version> |
| 41 | + <jedis-mock.version>1.0.4</jedis-mock.version> | ||
| 42 | + <mockito-inline.version>4.8.0</mockito-inline.version> | ||
| 43 | <!-- Bpm 工作流相关 --> | 43 | <!-- Bpm 工作流相关 --> |
| 44 | <flowable.version>6.7.2</flowable.version> | 44 | <flowable.version>6.7.2</flowable.version> |
| 45 | <!-- 工具类相关 --> | 45 | <!-- 工具类相关 --> |
| 46 | <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version> | 46 | <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version> |
| 47 | - <lombok.version>1.18.20</lombok.version> | ||
| 48 | - <mapstruct.version>1.4.1.Final</mapstruct.version> | ||
| 49 | - <hutool.version>5.8.5</hutool.version> | 47 | + <lombok.version>1.18.24</lombok.version> |
| 48 | + <mapstruct.version>1.5.2.Final</mapstruct.version> | ||
| 49 | + <hutool.version>5.8.8</hutool.version> | ||
| 50 | <easyexcel.verion>3.1.1</easyexcel.verion> | 50 | <easyexcel.verion>3.1.1</easyexcel.verion> |
| 51 | <velocity.version>2.3</velocity.version> | 51 | <velocity.version>2.3</velocity.version> |
| 52 | <screw.version>1.0.5</screw.version> | 52 | <screw.version>1.0.5</screw.version> |
| 53 | - <fastjson.version>1.2.83</fastjson.version> | ||
| 54 | - <guava.version>30.1.1-jre</guava.version> | 53 | + <fastjson.version>1.2.83</fastjson.version> |
| 54 | + <guava.version>31.1-jre</guava.version> | ||
| 55 | <guice.version>5.1.0</guice.version> | 55 | <guice.version>5.1.0</guice.version> |
| 56 | - <transmittable-thread-local.version>2.12.2</transmittable-thread-local.version> | 56 | + <transmittable-thread-local.version>2.14.0</transmittable-thread-local.version> |
| 57 | <commons-net.version>3.8.0</commons-net.version> | 57 | <commons-net.version>3.8.0</commons-net.version> |
| 58 | <jsch.version>0.1.55</jsch.version> | 58 | <jsch.version>0.1.55</jsch.version> |
| 59 | - <tika-core.version>2.4.1</tika-core.version> | 59 | + <tika-core.version>2.5.0</tika-core.version> |
| 60 | <aj-captcha.version>1.3.0</aj-captcha.version> | 60 | <aj-captcha.version>1.3.0</aj-captcha.version> |
| 61 | <netty-all.version>4.1.82.Final</netty-all.version> | 61 | <netty-all.version>4.1.82.Final</netty-all.version> |
| 62 | <!-- 三方云服务相关 --> | 62 | <!-- 三方云服务相关 --> |
| 63 | <minio.version>8.2.2</minio.version> | 63 | <minio.version>8.2.2</minio.version> |
| 64 | - <aliyun-java-sdk-core.version>4.6.0</aliyun-java-sdk-core.version> | 64 | + <aliyun-java-sdk-core.version>4.6.2</aliyun-java-sdk-core.version> |
| 65 | <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version> | 65 | <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version> |
| 66 | <tencentcloud-sdk-java.version>3.1.561</tencentcloud-sdk-java.version> | 66 | <tencentcloud-sdk-java.version>3.1.561</tencentcloud-sdk-java.version> |
| 67 | <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version> | 67 | <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version> |
| 68 | <justauth.version>1.4.0</justauth.version> | 68 | <justauth.version>1.4.0</justauth.version> |
| 69 | - <jimureport.version>1.5.2</jimureport.version> | ||
| 70 | - <xercesImpl.version>2.12.0</xercesImpl.version> | 69 | + <jimureport.version>1.5.3</jimureport.version> |
| 70 | + <xercesImpl.version>2.12.2</xercesImpl.version> | ||
| 71 | </properties> | 71 | </properties> |
| 72 | 72 | ||
| 73 | <dependencyManagement> | 73 | <dependencyManagement> |
yudao-example/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | + <modelVersion>4.0.0</modelVersion> | ||
| 6 | + | ||
| 7 | + <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent --> | ||
| 8 | + | ||
| 9 | + <groupId>cn.iocoder.boot</groupId> | ||
| 10 | + <artifactId>yudao-example</artifactId> | ||
| 11 | + <version>1.0.0-snapshot</version> | ||
| 12 | + <packaging>pom</packaging> | ||
| 13 | + <modules> | ||
| 14 | + <module>yudao-sso-demo-by-code</module> | ||
| 15 | + <module>yudao-sso-demo-by-password</module> | ||
| 16 | + </modules> | ||
| 17 | + | ||
| 18 | + <name>${project.artifactId}</name> | ||
| 19 | + <description>提供各种示例,例如说:SSO 单点登录</description> | ||
| 20 | + <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | ||
| 21 | + | ||
| 22 | +</project> |
yudao-example/yudao-sso-demo-by-code/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | + <modelVersion>4.0.0</modelVersion> | ||
| 6 | + | ||
| 7 | + <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent --> | ||
| 8 | + | ||
| 9 | + <groupId>cn.iocoder.boot</groupId> | ||
| 10 | + <artifactId>yudao-sso-demo-by-code</artifactId> | ||
| 11 | + <version>1.0.0-snapshot</version> | ||
| 12 | + <packaging>jar</packaging> | ||
| 13 | + | ||
| 14 | + <name>${project.artifactId}</name> | ||
| 15 | + <description>基于授权码模式,如何实现 SSO 单点登录?</description> | ||
| 16 | + <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | ||
| 17 | + | ||
| 18 | + <properties> | ||
| 19 | + <!-- Maven 相关 --> | ||
| 20 | + <maven.compiler.source>8</maven.compiler.source> | ||
| 21 | + <maven.compiler.target>8</maven.compiler.target> | ||
| 22 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| 23 | + <!-- 统一依赖管理 --> | ||
| 24 | + <spring.boot.version>2.6.10</spring.boot.version> | ||
| 25 | + </properties> | ||
| 26 | + | ||
| 27 | + <dependencyManagement> | ||
| 28 | + <dependencies> | ||
| 29 | + <!-- 统一依赖管理 --> | ||
| 30 | + <dependency> | ||
| 31 | + <groupId>org.springframework.boot</groupId> | ||
| 32 | + <artifactId>spring-boot-dependencies</artifactId> | ||
| 33 | + <version>${spring.boot.version}</version> | ||
| 34 | + <type>pom</type> | ||
| 35 | + <scope>import</scope> | ||
| 36 | + </dependency> | ||
| 37 | + </dependencies> | ||
| 38 | + </dependencyManagement> | ||
| 39 | + | ||
| 40 | + <dependencies> | ||
| 41 | + <!-- Web 相关 --> | ||
| 42 | + <dependency> | ||
| 43 | + <groupId>org.springframework.boot</groupId> | ||
| 44 | + <artifactId>spring-boot-starter-web</artifactId> | ||
| 45 | + </dependency> | ||
| 46 | + | ||
| 47 | + <dependency> | ||
| 48 | + <groupId>org.springframework.boot</groupId> | ||
| 49 | + <artifactId>spring-boot-starter-security</artifactId> | ||
| 50 | + </dependency> | ||
| 51 | + | ||
| 52 | + <dependency> | ||
| 53 | + <groupId>cn.hutool</groupId> | ||
| 54 | + <artifactId>hutool-all</artifactId> | ||
| 55 | + <version>5.8.8</version> | ||
| 56 | + </dependency> | ||
| 57 | + | ||
| 58 | + <dependency> | ||
| 59 | + <groupId>org.projectlombok</groupId> | ||
| 60 | + <artifactId>lombok</artifactId> | ||
| 61 | + <optional>true</optional> | ||
| 62 | + </dependency> | ||
| 63 | + </dependencies> | ||
| 64 | + | ||
| 65 | +</project> |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo; | ||
| 2 | + | ||
| 3 | +import org.springframework.boot.SpringApplication; | ||
| 4 | +import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
| 5 | + | ||
| 6 | +@SpringBootApplication | ||
| 7 | +public class SSODemoApplication { | ||
| 8 | + | ||
| 9 | + public static void main(String[] args) { | ||
| 10 | + SpringApplication.run(SSODemoApplication.class, args); | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO; | ||
| 6 | +import org.springframework.core.ParameterizedTypeReference; | ||
| 7 | +import org.springframework.http.*; | ||
| 8 | +import org.springframework.stereotype.Component; | ||
| 9 | +import org.springframework.util.Assert; | ||
| 10 | +import org.springframework.util.Base64Utils; | ||
| 11 | +import org.springframework.util.LinkedMultiValueMap; | ||
| 12 | +import org.springframework.util.MultiValueMap; | ||
| 13 | +import org.springframework.web.client.RestTemplate; | ||
| 14 | + | ||
| 15 | +import java.nio.charset.StandardCharsets; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * OAuth 2.0 客户端 | ||
| 19 | + * | ||
| 20 | + * 对应调用 OAuth2OpenController 接口 | ||
| 21 | + */ | ||
| 22 | +@Component | ||
| 23 | +public class OAuth2Client { | ||
| 24 | + | ||
| 25 | + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2"; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 租户编号 | ||
| 29 | + * | ||
| 30 | + * 默认使用 1;如果使用别的租户,可以调整 | ||
| 31 | + */ | ||
| 32 | + public static final Long TENANT_ID = 1L; | ||
| 33 | + | ||
| 34 | + private static final String CLIENT_ID = "yudao-sso-demo-by-code"; | ||
| 35 | + private static final String CLIENT_SECRET = "test"; | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 | ||
| 39 | + private final RestTemplate restTemplate = new RestTemplate(); | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 使用 code 授权码,获得访问令牌 | ||
| 43 | + * | ||
| 44 | + * @param code 授权码 | ||
| 45 | + * @param redirectUri 重定向 URI | ||
| 46 | + * @return 访问令牌 | ||
| 47 | + */ | ||
| 48 | + public CommonResult<OAuth2AccessTokenRespDTO> postAccessToken(String code, String redirectUri) { | ||
| 49 | + // 1.1 构建请求头 | ||
| 50 | + HttpHeaders headers = new HttpHeaders(); | ||
| 51 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 52 | + headers.set("tenant-id", TENANT_ID.toString()); | ||
| 53 | + addClientHeader(headers); | ||
| 54 | + // 1.2 构建请求参数 | ||
| 55 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 56 | + body.add("grant_type", "authorization_code"); | ||
| 57 | + body.add("code", code); | ||
| 58 | + body.add("redirect_uri", redirectUri); | ||
| 59 | +// body.add("state", ""); // 选填;填了会校验 | ||
| 60 | + | ||
| 61 | + // 2. 执行请求 | ||
| 62 | + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange( | ||
| 63 | + BASE_URL + "/token", | ||
| 64 | + HttpMethod.POST, | ||
| 65 | + new HttpEntity<>(body, headers), | ||
| 66 | + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 67 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 68 | + return exchange.getBody(); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * 校验访问令牌,并返回它的基本信息 | ||
| 73 | + * | ||
| 74 | + * @param token 访问令牌 | ||
| 75 | + * @return 访问令牌的基本信息 | ||
| 76 | + */ | ||
| 77 | + public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) { | ||
| 78 | + // 1.1 构建请求头 | ||
| 79 | + HttpHeaders headers = new HttpHeaders(); | ||
| 80 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 81 | + headers.set("tenant-id", TENANT_ID.toString()); | ||
| 82 | + addClientHeader(headers); | ||
| 83 | + // 1.2 构建请求参数 | ||
| 84 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 85 | + body.add("token", token); | ||
| 86 | + | ||
| 87 | + // 2. 执行请求 | ||
| 88 | + ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange( | ||
| 89 | + BASE_URL + "/check-token", | ||
| 90 | + HttpMethod.POST, | ||
| 91 | + new HttpEntity<>(body, headers), | ||
| 92 | + new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 93 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 94 | + return exchange.getBody(); | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + /** | ||
| 98 | + * 使用刷新令牌,获得(刷新)访问令牌 | ||
| 99 | + * | ||
| 100 | + * @param refreshToken 刷新令牌 | ||
| 101 | + * @return 访问令牌 | ||
| 102 | + */ | ||
| 103 | + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) { | ||
| 104 | + // 1.1 构建请求头 | ||
| 105 | + HttpHeaders headers = new HttpHeaders(); | ||
| 106 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 107 | + headers.set("tenant-id", TENANT_ID.toString()); | ||
| 108 | + addClientHeader(headers); | ||
| 109 | + // 1.2 构建请求参数 | ||
| 110 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 111 | + body.add("grant_type", "refresh_token"); | ||
| 112 | + body.add("refresh_token", refreshToken); | ||
| 113 | + | ||
| 114 | + // 2. 执行请求 | ||
| 115 | + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange( | ||
| 116 | + BASE_URL + "/token", | ||
| 117 | + HttpMethod.POST, | ||
| 118 | + new HttpEntity<>(body, headers), | ||
| 119 | + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 120 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 121 | + return exchange.getBody(); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + /** | ||
| 125 | + * 删除访问令牌 | ||
| 126 | + * | ||
| 127 | + * @param token 访问令牌 | ||
| 128 | + * @return 成功 | ||
| 129 | + */ | ||
| 130 | + public CommonResult<Boolean> revokeToken(String token) { | ||
| 131 | + // 1.1 构建请求头 | ||
| 132 | + HttpHeaders headers = new HttpHeaders(); | ||
| 133 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 134 | + headers.set("tenant-id", TENANT_ID.toString()); | ||
| 135 | + addClientHeader(headers); | ||
| 136 | + // 1.2 构建请求参数 | ||
| 137 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 138 | + body.add("token", token); | ||
| 139 | + | ||
| 140 | + // 2. 执行请求 | ||
| 141 | + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange( | ||
| 142 | + BASE_URL + "/token", | ||
| 143 | + HttpMethod.DELETE, | ||
| 144 | + new HttpEntity<>(body, headers), | ||
| 145 | + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 146 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 147 | + return exchange.getBody(); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + private static void addClientHeader(HttpHeaders headers) { | ||
| 151 | + // client 拼接,需要 BASE64 编码 | ||
| 152 | + String client = CLIENT_ID + ":" + CLIENT_SECRET; | ||
| 153 | + client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8)); | ||
| 154 | + headers.add("Authorization", "Basic " + client); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO; | ||
| 6 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | ||
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 8 | +import org.springframework.core.ParameterizedTypeReference; | ||
| 9 | +import org.springframework.http.*; | ||
| 10 | +import org.springframework.stereotype.Component; | ||
| 11 | +import org.springframework.util.Assert; | ||
| 12 | +import org.springframework.util.LinkedMultiValueMap; | ||
| 13 | +import org.springframework.util.MultiValueMap; | ||
| 14 | +import org.springframework.web.client.RestTemplate; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * 用户 User 信息的客户端 | ||
| 18 | + * | ||
| 19 | + * 对应调用 OAuth2UserController 接口 | ||
| 20 | + */ | ||
| 21 | +@Component | ||
| 22 | +public class UserClient { | ||
| 23 | + | ||
| 24 | + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user"; | ||
| 25 | + | ||
| 26 | + // @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 | ||
| 27 | + private final RestTemplate restTemplate = new RestTemplate(); | ||
| 28 | + | ||
| 29 | + public CommonResult<UserInfoRespDTO> getUser() { | ||
| 30 | + // 1.1 构建请求头 | ||
| 31 | + HttpHeaders headers = new HttpHeaders(); | ||
| 32 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 33 | + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString()); | ||
| 34 | + addTokenHeader(headers); | ||
| 35 | + // 1.2 构建请求参数 | ||
| 36 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 37 | + | ||
| 38 | + // 2. 执行请求 | ||
| 39 | + ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange( | ||
| 40 | + BASE_URL + "/get", | ||
| 41 | + HttpMethod.GET, | ||
| 42 | + new HttpEntity<>(body, headers), | ||
| 43 | + new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 44 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 45 | + return exchange.getBody(); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) { | ||
| 49 | + // 1.1 构建请求头 | ||
| 50 | + HttpHeaders headers = new HttpHeaders(); | ||
| 51 | + headers.setContentType(MediaType.APPLICATION_JSON); | ||
| 52 | + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString()); | ||
| 53 | + addTokenHeader(headers); | ||
| 54 | + // 1.2 构建请求参数 | ||
| 55 | + // 使用 updateReqDTO 即可 | ||
| 56 | + | ||
| 57 | + // 2. 执行请求 | ||
| 58 | + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange( | ||
| 59 | + BASE_URL + "/update", | ||
| 60 | + HttpMethod.PUT, | ||
| 61 | + new HttpEntity<>(updateReqDTO, headers), | ||
| 62 | + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 63 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 64 | + return exchange.getBody(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + | ||
| 68 | + private static void addTokenHeader(HttpHeaders headers) { | ||
| 69 | + LoginUser loginUser = SecurityUtils.getLoginUser(); | ||
| 70 | + Assert.notNull(loginUser, "登录用户不能为空"); | ||
| 71 | + headers.add("Authorization", "Bearer " + loginUser.getAccessToken()); | ||
| 72 | + } | ||
| 73 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +import java.io.Serializable; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 通用返回 | ||
| 9 | + * | ||
| 10 | + * @param <T> 数据泛型 | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +public class CommonResult<T> implements Serializable { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 错误码 | ||
| 17 | + */ | ||
| 18 | + private Integer code; | ||
| 19 | + /** | ||
| 20 | + * 返回数据 | ||
| 21 | + */ | ||
| 22 | + private T data; | ||
| 23 | + /** | ||
| 24 | + * 错误提示,用户可阅读 | ||
| 25 | + */ | ||
| 26 | + private String msg; | ||
| 27 | + | ||
| 28 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.oauth2; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | +import lombok.AllArgsConstructor; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import lombok.NoArgsConstructor; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 访问令牌 Response DTO | ||
| 10 | + */ | ||
| 11 | +@Data | ||
| 12 | +@NoArgsConstructor | ||
| 13 | +@AllArgsConstructor | ||
| 14 | +public class OAuth2AccessTokenRespDTO { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 访问令牌 | ||
| 18 | + */ | ||
| 19 | + @JsonProperty("access_token") | ||
| 20 | + private String accessToken; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 刷新令牌 | ||
| 24 | + */ | ||
| 25 | + @JsonProperty("refresh_token") | ||
| 26 | + private String refreshToken; | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 令牌类型 | ||
| 30 | + */ | ||
| 31 | + @JsonProperty("token_type") | ||
| 32 | + private String tokenType; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 过期时间;单位:秒 | ||
| 36 | + */ | ||
| 37 | + @JsonProperty("expires_in") | ||
| 38 | + private Long expiresIn; | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * 授权范围;如果多个授权范围,使用空格分隔 | ||
| 42 | + */ | ||
| 43 | + private String scope; | ||
| 44 | + | ||
| 45 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.oauth2; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | +import lombok.AllArgsConstructor; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import lombok.NoArgsConstructor; | ||
| 7 | + | ||
| 8 | +import java.util.List; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 校验令牌 Response DTO | ||
| 12 | + * | ||
| 13 | + * @author 芋道源码 | ||
| 14 | + */ | ||
| 15 | +@Data | ||
| 16 | +@NoArgsConstructor | ||
| 17 | +@AllArgsConstructor | ||
| 18 | +public class OAuth2CheckTokenRespDTO { | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 用户编号 | ||
| 22 | + */ | ||
| 23 | + @JsonProperty("user_id") | ||
| 24 | + private Long userId; | ||
| 25 | + /** | ||
| 26 | + * 用户类型 | ||
| 27 | + */ | ||
| 28 | + @JsonProperty("user_type") | ||
| 29 | + private Integer userType; | ||
| 30 | + /** | ||
| 31 | + * 租户编号 | ||
| 32 | + */ | ||
| 33 | + @JsonProperty("tenant_id") | ||
| 34 | + private Long tenantId; | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 客户端编号 | ||
| 38 | + */ | ||
| 39 | + @JsonProperty("client_id") | ||
| 40 | + private String clientId; | ||
| 41 | + /** | ||
| 42 | + * 授权范围 | ||
| 43 | + */ | ||
| 44 | + private List<String> scopes; | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 访问令牌 | ||
| 48 | + */ | ||
| 49 | + @JsonProperty("access_token") | ||
| 50 | + private String accessToken; | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * 过期时间 | ||
| 54 | + * | ||
| 55 | + * 时间戳 / 1000,即单位:秒 | ||
| 56 | + */ | ||
| 57 | + private Long exp; | ||
| 58 | + | ||
| 59 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.user; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import lombok.Data; | ||
| 5 | +import lombok.NoArgsConstructor; | ||
| 6 | + | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 获得用户基本信息 Response dto | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +@NoArgsConstructor | ||
| 14 | +@AllArgsConstructor | ||
| 15 | +public class UserInfoRespDTO { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 用户编号 | ||
| 19 | + */ | ||
| 20 | + private Long id; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 用户账号 | ||
| 24 | + */ | ||
| 25 | + private String username; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 用户昵称 | ||
| 29 | + */ | ||
| 30 | + private String nickname; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 用户邮箱 | ||
| 34 | + */ | ||
| 35 | + private String email; | ||
| 36 | + /** | ||
| 37 | + * 手机号码 | ||
| 38 | + */ | ||
| 39 | + private String mobile; | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 用户性别 | ||
| 43 | + */ | ||
| 44 | + private Integer sex; | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 用户头像 | ||
| 48 | + */ | ||
| 49 | + private String avatar; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 所在部门 | ||
| 53 | + */ | ||
| 54 | + private Dept dept; | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 所属岗位数组 | ||
| 58 | + */ | ||
| 59 | + private List<Post> posts; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * 部门 | ||
| 63 | + */ | ||
| 64 | + @Data | ||
| 65 | + public static class Dept { | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * 部门编号 | ||
| 69 | + */ | ||
| 70 | + private Long id; | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * 部门名称 | ||
| 74 | + */ | ||
| 75 | + private String name; | ||
| 76 | + | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * 岗位 | ||
| 81 | + */ | ||
| 82 | + @Data | ||
| 83 | + public static class Post { | ||
| 84 | + | ||
| 85 | + /** | ||
| 86 | + * 岗位编号 | ||
| 87 | + */ | ||
| 88 | + private Long id; | ||
| 89 | + | ||
| 90 | + /** | ||
| 91 | + * 岗位名称 | ||
| 92 | + */ | ||
| 93 | + private String name; | ||
| 94 | + | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.user; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import lombok.Data; | ||
| 5 | +import lombok.NoArgsConstructor; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 更新用户基本信息 Request DTO | ||
| 9 | + */ | ||
| 10 | +@Data | ||
| 11 | +@NoArgsConstructor | ||
| 12 | +@AllArgsConstructor | ||
| 13 | +public class UserUpdateReqDTO { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 用户昵称 | ||
| 17 | + */ | ||
| 18 | + private String nickname; | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 用户邮箱 | ||
| 22 | + */ | ||
| 23 | + private String email; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 手机号码 | ||
| 27 | + */ | ||
| 28 | + private String mobile; | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 用户性别 | ||
| 32 | + */ | ||
| 33 | + private Integer sex; | ||
| 34 | + | ||
| 35 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.controller; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.OAuth2Client; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 6 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | ||
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 8 | +import org.springframework.web.bind.annotation.PostMapping; | ||
| 9 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 10 | +import org.springframework.web.bind.annotation.RequestParam; | ||
| 11 | +import org.springframework.web.bind.annotation.RestController; | ||
| 12 | + | ||
| 13 | +import javax.annotation.Resource; | ||
| 14 | +import javax.servlet.http.HttpServletRequest; | ||
| 15 | + | ||
| 16 | +@RestController | ||
| 17 | +@RequestMapping("/auth") | ||
| 18 | +public class AuthController { | ||
| 19 | + | ||
| 20 | + @Resource | ||
| 21 | + private OAuth2Client oauth2Client; | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 使用 code 访问令牌,获得访问令牌 | ||
| 25 | + * | ||
| 26 | + * @param code 授权码 | ||
| 27 | + * @param redirectUri 重定向 URI | ||
| 28 | + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | ||
| 29 | + */ | ||
| 30 | + @PostMapping("/login-by-code") | ||
| 31 | + public CommonResult<OAuth2AccessTokenRespDTO> loginByCode(@RequestParam("code") String code, | ||
| 32 | + @RequestParam("redirectUri") String redirectUri) { | ||
| 33 | + return oauth2Client.postAccessToken(code, redirectUri); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 使用刷新令牌,获得(刷新)访问令牌 | ||
| 38 | + * | ||
| 39 | + * @param refreshToken 刷新令牌 | ||
| 40 | + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | ||
| 41 | + */ | ||
| 42 | + @PostMapping("/refresh-token") | ||
| 43 | + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) { | ||
| 44 | + return oauth2Client.refreshToken(refreshToken); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * 退出登录 | ||
| 49 | + * | ||
| 50 | + * @param request 请求 | ||
| 51 | + * @return 成功 | ||
| 52 | + */ | ||
| 53 | + @PostMapping("/logout") | ||
| 54 | + public CommonResult<Boolean> logout(HttpServletRequest request) { | ||
| 55 | + String token = SecurityUtils.obtainAuthorization(request, "Authorization"); | ||
| 56 | + if (StrUtil.isNotBlank(token)) { | ||
| 57 | + return oauth2Client.revokeToken(token); | ||
| 58 | + } | ||
| 59 | + // 返回成功 | ||
| 60 | + return new CommonResult<>(); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.controller; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.UserClient; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; | ||
| 6 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO; | ||
| 7 | +import org.springframework.web.bind.annotation.*; | ||
| 8 | + | ||
| 9 | +import javax.annotation.Resource; | ||
| 10 | + | ||
| 11 | +@RestController | ||
| 12 | +@RequestMapping("/user") | ||
| 13 | +public class UserController { | ||
| 14 | + | ||
| 15 | + @Resource | ||
| 16 | + private UserClient userClient; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 获得当前登录用户的基本信息 | ||
| 20 | + * | ||
| 21 | + * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | ||
| 22 | + */ | ||
| 23 | + @GetMapping("/get") | ||
| 24 | + public CommonResult<UserInfoRespDTO> getUser() { | ||
| 25 | + return userClient.getUser(); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 更新当前登录用户的昵称 | ||
| 30 | + * | ||
| 31 | + * @param nickname 昵称 | ||
| 32 | + * @return 成功 | ||
| 33 | + */ | ||
| 34 | + @PutMapping("/update") | ||
| 35 | + public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) { | ||
| 36 | + UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null); | ||
| 37 | + return userClient.updateUser(updateReqDTO); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.config; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl; | ||
| 5 | +import org.springframework.context.annotation.Configuration; | ||
| 6 | +import org.springframework.http.HttpMethod; | ||
| 7 | +import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| 8 | +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | ||
| 9 | +import org.springframework.security.web.AuthenticationEntryPoint; | ||
| 10 | +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
| 11 | + | ||
| 12 | +import javax.annotation.Resource; | ||
| 13 | + | ||
| 14 | +@Configuration | ||
| 15 | +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { | ||
| 16 | + | ||
| 17 | + @Resource | ||
| 18 | + private TokenAuthenticationFilter tokenAuthenticationFilter; | ||
| 19 | + | ||
| 20 | + @Resource | ||
| 21 | + private AccessDeniedHandlerImpl accessDeniedHandler; | ||
| 22 | + @Resource | ||
| 23 | + private AuthenticationEntryPoint authenticationEntryPoint; | ||
| 24 | + | ||
| 25 | + @Override | ||
| 26 | + protected void configure(HttpSecurity httpSecurity) throws Exception { | ||
| 27 | + // 设置 URL 安全权限 | ||
| 28 | + httpSecurity.csrf().disable() // 禁用 CSRF 保护 | ||
| 29 | + .authorizeRequests() | ||
| 30 | + // 1. 静态资源,可匿名访问 | ||
| 31 | + .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() | ||
| 32 | + // 2. 登录相关的接口,可匿名访问 | ||
| 33 | + .antMatchers("/auth/login-by-code").permitAll() | ||
| 34 | + .antMatchers("/auth/refresh-token").permitAll() | ||
| 35 | + .antMatchers("/auth/logout").permitAll() | ||
| 36 | + // last. 兜底规则,必须认证 | ||
| 37 | + .and().authorizeRequests() | ||
| 38 | + .anyRequest().authenticated(); | ||
| 39 | + | ||
| 40 | + // 设置处理器 | ||
| 41 | + httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler) | ||
| 42 | + .authenticationEntryPoint(authenticationEntryPoint); | ||
| 43 | + | ||
| 44 | + // 添加 Token Filter | ||
| 45 | + httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +import java.util.List; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 登录用户信息 | ||
| 9 | + * | ||
| 10 | + * @author 芋道源码 | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +public class LoginUser { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 用户编号 | ||
| 17 | + */ | ||
| 18 | + private Long id; | ||
| 19 | + /** | ||
| 20 | + * 用户类型 | ||
| 21 | + */ | ||
| 22 | + private Integer userType; | ||
| 23 | + /** | ||
| 24 | + * 租户编号 | ||
| 25 | + */ | ||
| 26 | + private Long tenantId; | ||
| 27 | + /** | ||
| 28 | + * 授权范围 | ||
| 29 | + */ | ||
| 30 | + private List<String> scopes; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 访问令牌 | ||
| 34 | + */ | ||
| 35 | + private String accessToken; | ||
| 36 | + | ||
| 37 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.filter; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.OAuth2Client; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO; | ||
| 6 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | ||
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 8 | +import org.springframework.stereotype.Component; | ||
| 9 | +import org.springframework.util.StringUtils; | ||
| 10 | +import org.springframework.web.filter.OncePerRequestFilter; | ||
| 11 | + | ||
| 12 | +import javax.annotation.Resource; | ||
| 13 | +import javax.servlet.FilterChain; | ||
| 14 | +import javax.servlet.ServletException; | ||
| 15 | +import javax.servlet.http.HttpServletRequest; | ||
| 16 | +import javax.servlet.http.HttpServletResponse; | ||
| 17 | +import java.io.IOException; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Token 过滤器,验证 token 的有效性 | ||
| 21 | + * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 | ||
| 22 | + * | ||
| 23 | + * @author 芋道源码 | ||
| 24 | + */ | ||
| 25 | +@Component | ||
| 26 | +public class TokenAuthenticationFilter extends OncePerRequestFilter { | ||
| 27 | + | ||
| 28 | + @Resource | ||
| 29 | + private OAuth2Client oauth2Client; | ||
| 30 | + | ||
| 31 | + @Override | ||
| 32 | + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | ||
| 33 | + FilterChain filterChain) throws ServletException, IOException { | ||
| 34 | + // 1. 获得访问令牌 | ||
| 35 | + String token = SecurityUtils.obtainAuthorization(request, "Authorization"); | ||
| 36 | + if (StringUtils.hasText(token)) { | ||
| 37 | + // 2. 基于 token 构建登录用户 | ||
| 38 | + LoginUser loginUser = buildLoginUserByToken(token); | ||
| 39 | + // 3. 设置当前用户 | ||
| 40 | + if (loginUser != null) { | ||
| 41 | + SecurityUtils.setLoginUser(loginUser, request); | ||
| 42 | + } | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + // 继续过滤链 | ||
| 46 | + filterChain.doFilter(request, response); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + private LoginUser buildLoginUserByToken(String token) { | ||
| 50 | + try { | ||
| 51 | + CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token); | ||
| 52 | + OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData(); | ||
| 53 | + if (accessToken == null) { | ||
| 54 | + return null; | ||
| 55 | + } | ||
| 56 | + // 构建登录用户 | ||
| 57 | + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) | ||
| 58 | + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()) | ||
| 59 | + .setAccessToken(accessToken.getAccessToken()); | ||
| 60 | + } catch (Exception exception) { | ||
| 61 | + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 | ||
| 62 | + return null; | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.handler; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils; | ||
| 6 | +import lombok.extern.slf4j.Slf4j; | ||
| 7 | +import org.springframework.http.HttpStatus; | ||
| 8 | +import org.springframework.security.access.AccessDeniedException; | ||
| 9 | +import org.springframework.security.web.access.AccessDeniedHandler; | ||
| 10 | +import org.springframework.security.web.access.ExceptionTranslationFilter; | ||
| 11 | +import org.springframework.stereotype.Component; | ||
| 12 | + | ||
| 13 | +import javax.servlet.FilterChain; | ||
| 14 | +import javax.servlet.ServletException; | ||
| 15 | +import javax.servlet.http.HttpServletRequest; | ||
| 16 | +import javax.servlet.http.HttpServletResponse; | ||
| 17 | +import java.io.IOException; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。 | ||
| 21 | + * | ||
| 22 | + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类 | ||
| 23 | + * | ||
| 24 | + * @author 芋道源码 | ||
| 25 | + */ | ||
| 26 | +@Component | ||
| 27 | +@SuppressWarnings("JavadocReference") | ||
| 28 | +@Slf4j | ||
| 29 | +public class AccessDeniedHandlerImpl implements AccessDeniedHandler { | ||
| 30 | + | ||
| 31 | + @Override | ||
| 32 | + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) | ||
| 33 | + throws IOException, ServletException { | ||
| 34 | + // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏 | ||
| 35 | + log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(), | ||
| 36 | + SecurityUtils.getLoginUserId(), e); | ||
| 37 | + // 返回 403 | ||
| 38 | + CommonResult<Object> result = new CommonResult<>(); | ||
| 39 | + result.setCode(HttpStatus.FORBIDDEN.value()); | ||
| 40 | + result.setMsg("没有该操作权限"); | ||
| 41 | + ServletUtils.writeJSON(response, result); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.handler; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils; | ||
| 5 | +import lombok.extern.slf4j.Slf4j; | ||
| 6 | +import org.springframework.http.HttpStatus; | ||
| 7 | +import org.springframework.security.core.AuthenticationException; | ||
| 8 | +import org.springframework.security.web.AuthenticationEntryPoint; | ||
| 9 | +import org.springframework.security.web.access.ExceptionTranslationFilter; | ||
| 10 | +import org.springframework.stereotype.Component; | ||
| 11 | + | ||
| 12 | +import javax.servlet.FilterChain; | ||
| 13 | +import javax.servlet.http.HttpServletRequest; | ||
| 14 | +import javax.servlet.http.HttpServletResponse; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页 | ||
| 18 | + * | ||
| 19 | + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类 | ||
| 20 | + */ | ||
| 21 | +@Component | ||
| 22 | +@Slf4j | ||
| 23 | +@SuppressWarnings("JavadocReference") // 忽略文档引用报错 | ||
| 24 | +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { | ||
| 25 | + | ||
| 26 | + @Override | ||
| 27 | + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { | ||
| 28 | + log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e); | ||
| 29 | + // 返回 401 | ||
| 30 | + CommonResult<Object> result = new CommonResult<>(); | ||
| 31 | + result.setCode(HttpStatus.UNAUTHORIZED.value()); | ||
| 32 | + result.setMsg("账号未登录"); | ||
| 33 | + ServletUtils.writeJSON(response, result); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.util; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | ||
| 4 | +import org.springframework.lang.Nullable; | ||
| 5 | +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
| 6 | +import org.springframework.security.core.Authentication; | ||
| 7 | +import org.springframework.security.core.context.SecurityContext; | ||
| 8 | +import org.springframework.security.core.context.SecurityContextHolder; | ||
| 9 | +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
| 10 | +import org.springframework.util.StringUtils; | ||
| 11 | + | ||
| 12 | +import javax.servlet.http.HttpServletRequest; | ||
| 13 | +import java.util.Collections; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * 安全服务工具类 | ||
| 17 | + * | ||
| 18 | + * @author 芋道源码 | ||
| 19 | + */ | ||
| 20 | +public class SecurityUtils { | ||
| 21 | + | ||
| 22 | + public static final String AUTHORIZATION_BEARER = "Bearer"; | ||
| 23 | + | ||
| 24 | + private SecurityUtils() {} | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 从请求中,获得认证 Token | ||
| 28 | + * | ||
| 29 | + * @param request 请求 | ||
| 30 | + * @param header 认证 Token 对应的 Header 名字 | ||
| 31 | + * @return 认证 Token | ||
| 32 | + */ | ||
| 33 | + public static String obtainAuthorization(HttpServletRequest request, String header) { | ||
| 34 | + String authorization = request.getHeader(header); | ||
| 35 | + if (!StringUtils.hasText(authorization)) { | ||
| 36 | + return null; | ||
| 37 | + } | ||
| 38 | + int index = authorization.indexOf(AUTHORIZATION_BEARER + " "); | ||
| 39 | + if (index == -1) { // 未找到 | ||
| 40 | + return null; | ||
| 41 | + } | ||
| 42 | + return authorization.substring(index + 7).trim(); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 获得当前认证信息 | ||
| 47 | + * | ||
| 48 | + * @return 认证信息 | ||
| 49 | + */ | ||
| 50 | + public static Authentication getAuthentication() { | ||
| 51 | + SecurityContext context = SecurityContextHolder.getContext(); | ||
| 52 | + if (context == null) { | ||
| 53 | + return null; | ||
| 54 | + } | ||
| 55 | + return context.getAuthentication(); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + /** | ||
| 59 | + * 获取当前用户 | ||
| 60 | + * | ||
| 61 | + * @return 当前用户 | ||
| 62 | + */ | ||
| 63 | + @Nullable | ||
| 64 | + public static LoginUser getLoginUser() { | ||
| 65 | + Authentication authentication = getAuthentication(); | ||
| 66 | + if (authentication == null) { | ||
| 67 | + return null; | ||
| 68 | + } | ||
| 69 | + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * 获得当前用户的编号,从上下文中 | ||
| 74 | + * | ||
| 75 | + * @return 用户编号 | ||
| 76 | + */ | ||
| 77 | + @Nullable | ||
| 78 | + public static Long getLoginUserId() { | ||
| 79 | + LoginUser loginUser = getLoginUser(); | ||
| 80 | + return loginUser != null ? loginUser.getId() : null; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + /** | ||
| 84 | + * 设置当前用户 | ||
| 85 | + * | ||
| 86 | + * @param loginUser 登录用户 | ||
| 87 | + * @param request 请求 | ||
| 88 | + */ | ||
| 89 | + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { | ||
| 90 | + // 创建 Authentication,并设置到上下文 | ||
| 91 | + Authentication authentication = buildAuthentication(loginUser, request); | ||
| 92 | + SecurityContextHolder.getContext().setAuthentication(authentication); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { | ||
| 96 | + // 创建 UsernamePasswordAuthenticationToken 对象 | ||
| 97 | + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( | ||
| 98 | + loginUser, null, Collections.emptyList()); | ||
| 99 | + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
| 100 | + return authenticationToken; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.util; | ||
| 2 | + | ||
| 3 | +import cn.hutool.extra.servlet.ServletUtil; | ||
| 4 | +import cn.hutool.json.JSONUtil; | ||
| 5 | +import org.springframework.http.MediaType; | ||
| 6 | + | ||
| 7 | +import javax.servlet.http.HttpServletResponse; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 客户端工具类 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +public class ServletUtils { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 返回 JSON 字符串 | ||
| 18 | + * | ||
| 19 | + * @param response 响应 | ||
| 20 | + * @param object 对象,会序列化成 JSON 字符串 | ||
| 21 | + */ | ||
| 22 | + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 | ||
| 23 | + public static void writeJSON(HttpServletResponse response, Object object) { | ||
| 24 | + String content = JSONUtil.toJsonStr(object); | ||
| 25 | + ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | +} |
yudao-example/yudao-sso-demo-by-code/src/main/resources/application.yaml
0 → 100644
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html
0 → 100644
| 1 | +<!DOCTYPE html> | ||
| 2 | +<html lang="en"> | ||
| 3 | +<head> | ||
| 4 | + <meta charset="UTF-8"> | ||
| 5 | + <title>SSO 授权后的回调页</title> | ||
| 6 | + <!-- jQuery:操作 dom、发起请求等 --> | ||
| 7 | + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> | ||
| 8 | + <!-- 工具类 --> | ||
| 9 | + <script type="application/javascript"> | ||
| 10 | + (function ($) { | ||
| 11 | + /** | ||
| 12 | + * 获得 URL 的指定参数的值 | ||
| 13 | + * | ||
| 14 | + * @param name 参数名 | ||
| 15 | + * @returns 参数值 | ||
| 16 | + */ | ||
| 17 | + $.getUrlParam = function (name) { | ||
| 18 | + const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); | ||
| 19 | + const r = window.location.search.substr(1).match(reg); | ||
| 20 | + if (r != null) return unescape(r[2]); return null; | ||
| 21 | + } | ||
| 22 | + })(jQuery); | ||
| 23 | + </script> | ||
| 24 | + | ||
| 25 | + <script type="application/javascript"> | ||
| 26 | + $(function () { | ||
| 27 | + // 获得 code 授权码 | ||
| 28 | + const code = $.getUrlParam('code'); | ||
| 29 | + if (!code) { | ||
| 30 | + alert('获取不到 code 参数,请排查!') | ||
| 31 | + return; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + // 提交 | ||
| 35 | + const redirectUri = 'http://127.0.0.1:18080/callback.html'; // 需要修改成,你回调的地址,就是在 index.html 拼接的 redirectUri | ||
| 36 | + $.ajax({ | ||
| 37 | + url: "http://127.0.0.1:18080/auth/login-by-code?code=" + code | ||
| 38 | + + '&redirectUri=' + redirectUri, | ||
| 39 | + method: 'POST', | ||
| 40 | + success: function( result ) { | ||
| 41 | + if (result.code !== 0) { | ||
| 42 | + alert('获得访问令牌失败,原因:' + result.msg) | ||
| 43 | + return; | ||
| 44 | + } | ||
| 45 | + alert('获得访问令牌成功!点击确认,跳转回首页') | ||
| 46 | + | ||
| 47 | + // 设置到 localStorage 中 | ||
| 48 | + localStorage.setItem('ACCESS-TOKEN', result.data.access_token); | ||
| 49 | + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token); | ||
| 50 | + | ||
| 51 | + // 跳转回首页 | ||
| 52 | + window.location.href = '/index.html'; | ||
| 53 | + } | ||
| 54 | + }) | ||
| 55 | + }) | ||
| 56 | + </script> | ||
| 57 | +</head> | ||
| 58 | +<body> | ||
| 59 | +正在使用 code 授权码,进行 accessToken 访问令牌的获取 | ||
| 60 | +</body> | ||
| 61 | +</html> |
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
0 → 100644
| 1 | +<!DOCTYPE html> | ||
| 2 | +<html lang="en"> | ||
| 3 | +<head> | ||
| 4 | + <meta charset="UTF-8"> | ||
| 5 | + <title>首页</title> | ||
| 6 | + <!-- jQuery:操作 dom、发起请求等 --> | ||
| 7 | + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> | ||
| 8 | + | ||
| 9 | + <script type="application/javascript"> | ||
| 10 | + | ||
| 11 | + /** | ||
| 12 | + * 跳转单点登录 | ||
| 13 | + */ | ||
| 14 | + function ssoLogin() { | ||
| 15 | + const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId | ||
| 16 | + const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback.html'); // 注意,需要使用 encodeURIComponent 编码地址 | ||
| 17 | + const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token | ||
| 18 | + window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId | ||
| 19 | + + '&redirect_uri=' + redirectUri | ||
| 20 | + + '&response_type=' + responseType; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 修改昵称 | ||
| 25 | + */ | ||
| 26 | + function updateNickname() { | ||
| 27 | + const nickname = prompt("请输入新的昵称", ""); | ||
| 28 | + if (!nickname) { | ||
| 29 | + return; | ||
| 30 | + } | ||
| 31 | + // 更新用户的昵称 | ||
| 32 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||
| 33 | + $.ajax({ | ||
| 34 | + url: "http://127.0.0.1:18080/user/update?nickname=" + nickname, | ||
| 35 | + method: 'PUT', | ||
| 36 | + headers: { | ||
| 37 | + 'Authorization': 'Bearer ' + accessToken | ||
| 38 | + }, | ||
| 39 | + success: function (result) { | ||
| 40 | + if (result.code !== 0) { | ||
| 41 | + alert('更新昵称失败,原因:' + result.msg) | ||
| 42 | + return; | ||
| 43 | + } | ||
| 44 | + alert('更新昵称成功!'); | ||
| 45 | + $('#nicknameSpan').html(nickname); | ||
| 46 | + } | ||
| 47 | + }); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + /** | ||
| 51 | + * 刷新令牌 | ||
| 52 | + */ | ||
| 53 | + function refreshToken() { | ||
| 54 | + const refreshToken = localStorage.getItem('REFRESH-TOKEN'); | ||
| 55 | + if (!refreshToken) { | ||
| 56 | + alert("获取不到刷新令牌"); | ||
| 57 | + return; | ||
| 58 | + } | ||
| 59 | + $.ajax({ | ||
| 60 | + url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken, | ||
| 61 | + method: 'POST', | ||
| 62 | + success: function (result) { | ||
| 63 | + if (result.code !== 0) { | ||
| 64 | + alert('刷新访问令牌失败,原因:' + result.msg) | ||
| 65 | + return; | ||
| 66 | + } | ||
| 67 | + alert('更新访问令牌成功!'); | ||
| 68 | + $('#accessTokenSpan').html(result.data.access_token); | ||
| 69 | + | ||
| 70 | + // 设置到 localStorage 中 | ||
| 71 | + localStorage.setItem('ACCESS-TOKEN', result.data.access_token); | ||
| 72 | + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token); | ||
| 73 | + } | ||
| 74 | + }); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + /** | ||
| 78 | + * 登出,删除访问令牌 | ||
| 79 | + */ | ||
| 80 | + function logout() { | ||
| 81 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||
| 82 | + if (!accessToken) { | ||
| 83 | + location.reload(); | ||
| 84 | + return; | ||
| 85 | + } | ||
| 86 | + $.ajax({ | ||
| 87 | + url: "http://127.0.0.1:18080/auth/logout", | ||
| 88 | + method: 'POST', | ||
| 89 | + headers: { | ||
| 90 | + 'Authorization': 'Bearer ' + accessToken | ||
| 91 | + }, | ||
| 92 | + success: function (result) { | ||
| 93 | + if (result.code !== 0) { | ||
| 94 | + alert('退出登录失败,原因:' + result.msg) | ||
| 95 | + return; | ||
| 96 | + } | ||
| 97 | + alert('退出登录成功!'); | ||
| 98 | + // 删除 localStorage 中 | ||
| 99 | + localStorage.removeItem('ACCESS-TOKEN'); | ||
| 100 | + localStorage.removeItem('REFRESH-TOKEN'); | ||
| 101 | + | ||
| 102 | + location.reload(); | ||
| 103 | + } | ||
| 104 | + }); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + $(function () { | ||
| 108 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||
| 109 | + // 情况一:未登录 | ||
| 110 | + if (!accessToken) { | ||
| 111 | + $('#noLoginDiv').css("display", "block"); | ||
| 112 | + return; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + // 情况二:已登录 | ||
| 116 | + $('#yesLoginDiv').css("display", "block"); | ||
| 117 | + $('#accessTokenSpan').html(accessToken); | ||
| 118 | + // 获得登录用户的信息 | ||
| 119 | + $.ajax({ | ||
| 120 | + url: "http://127.0.0.1:18080/user/get", | ||
| 121 | + method: 'GET', | ||
| 122 | + headers: { | ||
| 123 | + 'Authorization': 'Bearer ' + accessToken | ||
| 124 | + }, | ||
| 125 | + success: function (result) { | ||
| 126 | + if (result.code !== 0) { | ||
| 127 | + alert('获得个人信息失败,原因:' + result.msg) | ||
| 128 | + return; | ||
| 129 | + } | ||
| 130 | + $('#nicknameSpan').html(result.data.nickname); | ||
| 131 | + } | ||
| 132 | + }); | ||
| 133 | + }) | ||
| 134 | + </script> | ||
| 135 | +</head> | ||
| 136 | +<body> | ||
| 137 | +<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 --> | ||
| 138 | +<div id="noLoginDiv" style="display: none"> | ||
| 139 | + 您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录 | ||
| 140 | +</div> | ||
| 141 | + | ||
| 142 | +<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> | ||
| 143 | +<div id="yesLoginDiv" style="display: none"> | ||
| 144 | + 您已登录!<button onclick="logout()">退出登录</button> <br /> | ||
| 145 | + 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br /> | ||
| 146 | + 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br /> | ||
| 147 | +</div> | ||
| 148 | +</body> | ||
| 149 | +<style> | ||
| 150 | + body { /** 页面居中 */ | ||
| 151 | + border-radius: 20px; | ||
| 152 | + height: 350px; | ||
| 153 | + position: absolute; | ||
| 154 | + left: 50%; | ||
| 155 | + top: 50%; | ||
| 156 | + transform: translate(-50%,-50%); | ||
| 157 | + } | ||
| 158 | +</style> | ||
| 159 | +</html> |
yudao-example/yudao-sso-demo-by-password/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | + <modelVersion>4.0.0</modelVersion> | ||
| 6 | + | ||
| 7 | + <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent --> | ||
| 8 | + | ||
| 9 | + <groupId>cn.iocoder.boot</groupId> | ||
| 10 | + <artifactId>yudao-sso-demo-by-password</artifactId> | ||
| 11 | + <version>1.0.0-snapshot</version> | ||
| 12 | + <packaging>jar</packaging> | ||
| 13 | + | ||
| 14 | + <name>${project.artifactId}</name> | ||
| 15 | + <description>基于密码模式,如何实现 SSO 单点登录?</description> | ||
| 16 | + <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | ||
| 17 | + | ||
| 18 | + <properties> | ||
| 19 | + <!-- Maven 相关 --> | ||
| 20 | + <maven.compiler.source>8</maven.compiler.source> | ||
| 21 | + <maven.compiler.target>8</maven.compiler.target> | ||
| 22 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| 23 | + <!-- 统一依赖管理 --> | ||
| 24 | + <spring.boot.version>2.6.10</spring.boot.version> | ||
| 25 | + </properties> | ||
| 26 | + | ||
| 27 | + <dependencyManagement> | ||
| 28 | + <dependencies> | ||
| 29 | + <!-- 统一依赖管理 --> | ||
| 30 | + <dependency> | ||
| 31 | + <groupId>org.springframework.boot</groupId> | ||
| 32 | + <artifactId>spring-boot-dependencies</artifactId> | ||
| 33 | + <version>${spring.boot.version}</version> | ||
| 34 | + <type>pom</type> | ||
| 35 | + <scope>import</scope> | ||
| 36 | + </dependency> | ||
| 37 | + </dependencies> | ||
| 38 | + </dependencyManagement> | ||
| 39 | + | ||
| 40 | + <dependencies> | ||
| 41 | + <!-- Web 相关 --> | ||
| 42 | + <dependency> | ||
| 43 | + <groupId>org.springframework.boot</groupId> | ||
| 44 | + <artifactId>spring-boot-starter-web</artifactId> | ||
| 45 | + </dependency> | ||
| 46 | + | ||
| 47 | + <dependency> | ||
| 48 | + <groupId>org.springframework.boot</groupId> | ||
| 49 | + <artifactId>spring-boot-starter-security</artifactId> | ||
| 50 | + </dependency> | ||
| 51 | + | ||
| 52 | + <dependency> | ||
| 53 | + <groupId>cn.hutool</groupId> | ||
| 54 | + <artifactId>hutool-all</artifactId> | ||
| 55 | + <version>5.8.8</version> | ||
| 56 | + </dependency> | ||
| 57 | + | ||
| 58 | + <dependency> | ||
| 59 | + <groupId>org.projectlombok</groupId> | ||
| 60 | + <artifactId>lombok</artifactId> | ||
| 61 | + <optional>true</optional> | ||
| 62 | + </dependency> | ||
| 63 | + </dependencies> | ||
| 64 | + | ||
| 65 | +</project> |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo; | ||
| 2 | + | ||
| 3 | +import org.springframework.boot.SpringApplication; | ||
| 4 | +import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
| 5 | + | ||
| 6 | +@SpringBootApplication | ||
| 7 | +public class SSODemoApplication { | ||
| 8 | + | ||
| 9 | + public static void main(String[] args) { | ||
| 10 | + SpringApplication.run(SSODemoApplication.class, args); | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO; | ||
| 6 | +import org.springframework.core.ParameterizedTypeReference; | ||
| 7 | +import org.springframework.http.*; | ||
| 8 | +import org.springframework.stereotype.Component; | ||
| 9 | +import org.springframework.util.Assert; | ||
| 10 | +import org.springframework.util.Base64Utils; | ||
| 11 | +import org.springframework.util.LinkedMultiValueMap; | ||
| 12 | +import org.springframework.util.MultiValueMap; | ||
| 13 | +import org.springframework.web.client.RestTemplate; | ||
| 14 | + | ||
| 15 | +import java.nio.charset.StandardCharsets; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * OAuth 2.0 客户端 | ||
| 19 | + * | ||
| 20 | + * 对应调用 OAuth2OpenController 接口 | ||
| 21 | + */ | ||
| 22 | +@Component | ||
| 23 | +public class OAuth2Client { | ||
| 24 | + | ||
| 25 | + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2"; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 租户编号 | ||
| 29 | + * | ||
| 30 | + * 默认使用 1;如果使用别的租户,可以调整 | ||
| 31 | + */ | ||
| 32 | + public static final Long TENANT_ID = 1L; | ||
| 33 | + | ||
| 34 | + private static final String CLIENT_ID = "yudao-sso-demo-by-password"; | ||
| 35 | + private static final String CLIENT_SECRET = "test"; | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 | ||
| 39 | + private final RestTemplate restTemplate = new RestTemplate(); | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 校验访问令牌,并返回它的基本信息 | ||
| 43 | + * | ||
| 44 | + * @param token 访问令牌 | ||
| 45 | + * @return 访问令牌的基本信息 | ||
| 46 | + */ | ||
| 47 | + public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) { | ||
| 48 | + // 1.1 构建请求头 | ||
| 49 | + HttpHeaders headers = new HttpHeaders(); | ||
| 50 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 51 | + headers.set("tenant-id", TENANT_ID.toString()); | ||
| 52 | + addClientHeader(headers); | ||
| 53 | + // 1.2 构建请求参数 | ||
| 54 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 55 | + body.add("token", token); | ||
| 56 | + | ||
| 57 | + // 2. 执行请求 | ||
| 58 | + ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange( | ||
| 59 | + BASE_URL + "/check-token", | ||
| 60 | + HttpMethod.POST, | ||
| 61 | + new HttpEntity<>(body, headers), | ||
| 62 | + new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 63 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 64 | + return exchange.getBody(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * 使用刷新令牌,获得(刷新)访问令牌 | ||
| 69 | + * | ||
| 70 | + * @param refreshToken 刷新令牌 | ||
| 71 | + * @return 访问令牌 | ||
| 72 | + */ | ||
| 73 | + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) { | ||
| 74 | + // 1.1 构建请求头 | ||
| 75 | + HttpHeaders headers = new HttpHeaders(); | ||
| 76 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 77 | + headers.set("tenant-id", TENANT_ID.toString()); | ||
| 78 | + addClientHeader(headers); | ||
| 79 | + // 1.2 构建请求参数 | ||
| 80 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 81 | + body.add("grant_type", "refresh_token"); | ||
| 82 | + body.add("refresh_token", refreshToken); | ||
| 83 | + | ||
| 84 | + // 2. 执行请求 | ||
| 85 | + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange( | ||
| 86 | + BASE_URL + "/token", | ||
| 87 | + HttpMethod.POST, | ||
| 88 | + new HttpEntity<>(body, headers), | ||
| 89 | + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 90 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 91 | + return exchange.getBody(); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + /** | ||
| 95 | + * 删除访问令牌 | ||
| 96 | + * | ||
| 97 | + * @param token 访问令牌 | ||
| 98 | + * @return 成功 | ||
| 99 | + */ | ||
| 100 | + public CommonResult<Boolean> revokeToken(String token) { | ||
| 101 | + // 1.1 构建请求头 | ||
| 102 | + HttpHeaders headers = new HttpHeaders(); | ||
| 103 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 104 | + headers.set("tenant-id", TENANT_ID.toString()); | ||
| 105 | + addClientHeader(headers); | ||
| 106 | + // 1.2 构建请求参数 | ||
| 107 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 108 | + body.add("token", token); | ||
| 109 | + | ||
| 110 | + // 2. 执行请求 | ||
| 111 | + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange( | ||
| 112 | + BASE_URL + "/token", | ||
| 113 | + HttpMethod.DELETE, | ||
| 114 | + new HttpEntity<>(body, headers), | ||
| 115 | + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 116 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 117 | + return exchange.getBody(); | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + private static void addClientHeader(HttpHeaders headers) { | ||
| 121 | + // client 拼接,需要 BASE64 编码 | ||
| 122 | + String client = CLIENT_ID + ":" + CLIENT_SECRET; | ||
| 123 | + client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8)); | ||
| 124 | + headers.add("Authorization", "Basic " + client); | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO; | ||
| 6 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | ||
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 8 | +import org.springframework.core.ParameterizedTypeReference; | ||
| 9 | +import org.springframework.http.*; | ||
| 10 | +import org.springframework.stereotype.Component; | ||
| 11 | +import org.springframework.util.Assert; | ||
| 12 | +import org.springframework.util.LinkedMultiValueMap; | ||
| 13 | +import org.springframework.util.MultiValueMap; | ||
| 14 | +import org.springframework.web.client.RestTemplate; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * 用户 User 信息的客户端 | ||
| 18 | + * | ||
| 19 | + * 对应调用 OAuth2UserController 接口 | ||
| 20 | + */ | ||
| 21 | +@Component | ||
| 22 | +public class UserClient { | ||
| 23 | + | ||
| 24 | + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user"; | ||
| 25 | + | ||
| 26 | + // @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 | ||
| 27 | + private final RestTemplate restTemplate = new RestTemplate(); | ||
| 28 | + | ||
| 29 | + public CommonResult<UserInfoRespDTO> getUser() { | ||
| 30 | + // 1.1 构建请求头 | ||
| 31 | + HttpHeaders headers = new HttpHeaders(); | ||
| 32 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 33 | + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString()); | ||
| 34 | + addTokenHeader(headers); | ||
| 35 | + // 1.2 构建请求参数 | ||
| 36 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 37 | + | ||
| 38 | + // 2. 执行请求 | ||
| 39 | + ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange( | ||
| 40 | + BASE_URL + "/get", | ||
| 41 | + HttpMethod.GET, | ||
| 42 | + new HttpEntity<>(body, headers), | ||
| 43 | + new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 44 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 45 | + return exchange.getBody(); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) { | ||
| 49 | + // 1.1 构建请求头 | ||
| 50 | + HttpHeaders headers = new HttpHeaders(); | ||
| 51 | + headers.setContentType(MediaType.APPLICATION_JSON); | ||
| 52 | + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString()); | ||
| 53 | + addTokenHeader(headers); | ||
| 54 | + // 1.2 构建请求参数 | ||
| 55 | + // 使用 updateReqDTO 即可 | ||
| 56 | + | ||
| 57 | + // 2. 执行请求 | ||
| 58 | + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange( | ||
| 59 | + BASE_URL + "/update", | ||
| 60 | + HttpMethod.PUT, | ||
| 61 | + new HttpEntity<>(updateReqDTO, headers), | ||
| 62 | + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 63 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 64 | + return exchange.getBody(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + | ||
| 68 | + private static void addTokenHeader(HttpHeaders headers) { | ||
| 69 | + LoginUser loginUser = SecurityUtils.getLoginUser(); | ||
| 70 | + Assert.notNull(loginUser, "登录用户不能为空"); | ||
| 71 | + headers.add("Authorization", "Bearer " + loginUser.getAccessToken()); | ||
| 72 | + } | ||
| 73 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +import java.io.Serializable; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 通用返回 | ||
| 9 | + * | ||
| 10 | + * @param <T> 数据泛型 | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +public class CommonResult<T> implements Serializable { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 错误码 | ||
| 17 | + */ | ||
| 18 | + private Integer code; | ||
| 19 | + /** | ||
| 20 | + * 返回数据 | ||
| 21 | + */ | ||
| 22 | + private T data; | ||
| 23 | + /** | ||
| 24 | + * 错误提示,用户可阅读 | ||
| 25 | + */ | ||
| 26 | + private String msg; | ||
| 27 | + | ||
| 28 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.oauth2; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | +import lombok.AllArgsConstructor; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import lombok.NoArgsConstructor; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 访问令牌 Response DTO | ||
| 10 | + */ | ||
| 11 | +@Data | ||
| 12 | +@NoArgsConstructor | ||
| 13 | +@AllArgsConstructor | ||
| 14 | +public class OAuth2AccessTokenRespDTO { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 访问令牌 | ||
| 18 | + */ | ||
| 19 | + @JsonProperty("access_token") | ||
| 20 | + private String accessToken; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 刷新令牌 | ||
| 24 | + */ | ||
| 25 | + @JsonProperty("refresh_token") | ||
| 26 | + private String refreshToken; | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 令牌类型 | ||
| 30 | + */ | ||
| 31 | + @JsonProperty("token_type") | ||
| 32 | + private String tokenType; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 过期时间;单位:秒 | ||
| 36 | + */ | ||
| 37 | + @JsonProperty("expires_in") | ||
| 38 | + private Long expiresIn; | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * 授权范围;如果多个授权范围,使用空格分隔 | ||
| 42 | + */ | ||
| 43 | + private String scope; | ||
| 44 | + | ||
| 45 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.oauth2; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | +import lombok.AllArgsConstructor; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import lombok.NoArgsConstructor; | ||
| 7 | + | ||
| 8 | +import java.util.List; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 校验令牌 Response DTO | ||
| 12 | + * | ||
| 13 | + * @author 芋道源码 | ||
| 14 | + */ | ||
| 15 | +@Data | ||
| 16 | +@NoArgsConstructor | ||
| 17 | +@AllArgsConstructor | ||
| 18 | +public class OAuth2CheckTokenRespDTO { | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 用户编号 | ||
| 22 | + */ | ||
| 23 | + @JsonProperty("user_id") | ||
| 24 | + private Long userId; | ||
| 25 | + /** | ||
| 26 | + * 用户类型 | ||
| 27 | + */ | ||
| 28 | + @JsonProperty("user_type") | ||
| 29 | + private Integer userType; | ||
| 30 | + /** | ||
| 31 | + * 租户编号 | ||
| 32 | + */ | ||
| 33 | + @JsonProperty("tenant_id") | ||
| 34 | + private Long tenantId; | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 客户端编号 | ||
| 38 | + */ | ||
| 39 | + @JsonProperty("client_id") | ||
| 40 | + private String clientId; | ||
| 41 | + /** | ||
| 42 | + * 授权范围 | ||
| 43 | + */ | ||
| 44 | + private List<String> scopes; | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 访问令牌 | ||
| 48 | + */ | ||
| 49 | + @JsonProperty("access_token") | ||
| 50 | + private String accessToken; | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * 过期时间 | ||
| 54 | + * | ||
| 55 | + * 时间戳 / 1000,即单位:秒 | ||
| 56 | + */ | ||
| 57 | + private Long exp; | ||
| 58 | + | ||
| 59 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.user; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import lombok.Data; | ||
| 5 | +import lombok.NoArgsConstructor; | ||
| 6 | + | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 获得用户基本信息 Response dto | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +@NoArgsConstructor | ||
| 14 | +@AllArgsConstructor | ||
| 15 | +public class UserInfoRespDTO { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 用户编号 | ||
| 19 | + */ | ||
| 20 | + private Long id; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 用户账号 | ||
| 24 | + */ | ||
| 25 | + private String username; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 用户昵称 | ||
| 29 | + */ | ||
| 30 | + private String nickname; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 用户邮箱 | ||
| 34 | + */ | ||
| 35 | + private String email; | ||
| 36 | + /** | ||
| 37 | + * 手机号码 | ||
| 38 | + */ | ||
| 39 | + private String mobile; | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 用户性别 | ||
| 43 | + */ | ||
| 44 | + private Integer sex; | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 用户头像 | ||
| 48 | + */ | ||
| 49 | + private String avatar; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 所在部门 | ||
| 53 | + */ | ||
| 54 | + private Dept dept; | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 所属岗位数组 | ||
| 58 | + */ | ||
| 59 | + private List<Post> posts; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * 部门 | ||
| 63 | + */ | ||
| 64 | + @Data | ||
| 65 | + public static class Dept { | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * 部门编号 | ||
| 69 | + */ | ||
| 70 | + private Long id; | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * 部门名称 | ||
| 74 | + */ | ||
| 75 | + private String name; | ||
| 76 | + | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * 岗位 | ||
| 81 | + */ | ||
| 82 | + @Data | ||
| 83 | + public static class Post { | ||
| 84 | + | ||
| 85 | + /** | ||
| 86 | + * 岗位编号 | ||
| 87 | + */ | ||
| 88 | + private Long id; | ||
| 89 | + | ||
| 90 | + /** | ||
| 91 | + * 岗位名称 | ||
| 92 | + */ | ||
| 93 | + private String name; | ||
| 94 | + | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.user; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import lombok.Data; | ||
| 5 | +import lombok.NoArgsConstructor; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 更新用户基本信息 Request DTO | ||
| 9 | + */ | ||
| 10 | +@Data | ||
| 11 | +@NoArgsConstructor | ||
| 12 | +@AllArgsConstructor | ||
| 13 | +public class UserUpdateReqDTO { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 用户昵称 | ||
| 17 | + */ | ||
| 18 | + private String nickname; | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 用户邮箱 | ||
| 22 | + */ | ||
| 23 | + private String email; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 手机号码 | ||
| 27 | + */ | ||
| 28 | + private String mobile; | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 用户性别 | ||
| 32 | + */ | ||
| 33 | + private Integer sex; | ||
| 34 | + | ||
| 35 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.controller; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.OAuth2Client; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 6 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | ||
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 8 | +import org.springframework.web.bind.annotation.PostMapping; | ||
| 9 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 10 | +import org.springframework.web.bind.annotation.RequestParam; | ||
| 11 | +import org.springframework.web.bind.annotation.RestController; | ||
| 12 | + | ||
| 13 | +import javax.annotation.Resource; | ||
| 14 | +import javax.servlet.http.HttpServletRequest; | ||
| 15 | + | ||
| 16 | +@RestController | ||
| 17 | +@RequestMapping("/auth") | ||
| 18 | +public class AuthController { | ||
| 19 | + | ||
| 20 | + @Resource | ||
| 21 | + private OAuth2Client oauth2Client; | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 使用刷新令牌,获得(刷新)访问令牌 | ||
| 25 | + * | ||
| 26 | + * @param refreshToken 刷新令牌 | ||
| 27 | + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | ||
| 28 | + */ | ||
| 29 | + @PostMapping("/refresh-token") | ||
| 30 | + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) { | ||
| 31 | + return oauth2Client.refreshToken(refreshToken); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 退出登录 | ||
| 36 | + * | ||
| 37 | + * @param request 请求 | ||
| 38 | + * @return 成功 | ||
| 39 | + */ | ||
| 40 | + @PostMapping("/logout") | ||
| 41 | + public CommonResult<Boolean> logout(HttpServletRequest request) { | ||
| 42 | + String token = SecurityUtils.obtainAuthorization(request, "Authorization"); | ||
| 43 | + if (StrUtil.isNotBlank(token)) { | ||
| 44 | + return oauth2Client.revokeToken(token); | ||
| 45 | + } | ||
| 46 | + // 返回成功 | ||
| 47 | + return new CommonResult<>(); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.controller; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.UserClient; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; | ||
| 6 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO; | ||
| 7 | +import org.springframework.web.bind.annotation.*; | ||
| 8 | + | ||
| 9 | +import javax.annotation.Resource; | ||
| 10 | + | ||
| 11 | +@RestController | ||
| 12 | +@RequestMapping("/user") | ||
| 13 | +public class UserController { | ||
| 14 | + | ||
| 15 | + @Resource | ||
| 16 | + private UserClient userClient; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 获得当前登录用户的基本信息 | ||
| 20 | + * | ||
| 21 | + * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | ||
| 22 | + */ | ||
| 23 | + @GetMapping("/get") | ||
| 24 | + public CommonResult<UserInfoRespDTO> getUser() { | ||
| 25 | + return userClient.getUser(); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 更新当前登录用户的昵称 | ||
| 30 | + * | ||
| 31 | + * @param nickname 昵称 | ||
| 32 | + * @return 成功 | ||
| 33 | + */ | ||
| 34 | + @PutMapping("/update") | ||
| 35 | + public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) { | ||
| 36 | + UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null); | ||
| 37 | + return userClient.updateUser(updateReqDTO); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.config; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl; | ||
| 5 | +import org.springframework.context.annotation.Configuration; | ||
| 6 | +import org.springframework.http.HttpMethod; | ||
| 7 | +import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| 8 | +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | ||
| 9 | +import org.springframework.security.web.AuthenticationEntryPoint; | ||
| 10 | +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
| 11 | + | ||
| 12 | +import javax.annotation.Resource; | ||
| 13 | + | ||
| 14 | +@Configuration | ||
| 15 | +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { | ||
| 16 | + | ||
| 17 | + @Resource | ||
| 18 | + private TokenAuthenticationFilter tokenAuthenticationFilter; | ||
| 19 | + | ||
| 20 | + @Resource | ||
| 21 | + private AccessDeniedHandlerImpl accessDeniedHandler; | ||
| 22 | + @Resource | ||
| 23 | + private AuthenticationEntryPoint authenticationEntryPoint; | ||
| 24 | + | ||
| 25 | + @Override | ||
| 26 | + protected void configure(HttpSecurity httpSecurity) throws Exception { | ||
| 27 | + // 设置 URL 安全权限 | ||
| 28 | + httpSecurity.csrf().disable() // 禁用 CSRF 保护 | ||
| 29 | + .authorizeRequests() | ||
| 30 | + // 1. 静态资源,可匿名访问 | ||
| 31 | + .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() | ||
| 32 | + // 2. 登录相关的接口,可匿名访问 | ||
| 33 | + .antMatchers("/auth/login-by-code").permitAll() | ||
| 34 | + .antMatchers("/auth/refresh-token").permitAll() | ||
| 35 | + .antMatchers("/auth/logout").permitAll() | ||
| 36 | + // last. 兜底规则,必须认证 | ||
| 37 | + .and().authorizeRequests() | ||
| 38 | + .anyRequest().authenticated(); | ||
| 39 | + | ||
| 40 | + // 设置处理器 | ||
| 41 | + httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler) | ||
| 42 | + .authenticationEntryPoint(authenticationEntryPoint); | ||
| 43 | + | ||
| 44 | + // 添加 Token Filter | ||
| 45 | + httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +import java.util.List; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 登录用户信息 | ||
| 9 | + * | ||
| 10 | + * @author 芋道源码 | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +public class LoginUser { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 用户编号 | ||
| 17 | + */ | ||
| 18 | + private Long id; | ||
| 19 | + /** | ||
| 20 | + * 用户类型 | ||
| 21 | + */ | ||
| 22 | + private Integer userType; | ||
| 23 | + /** | ||
| 24 | + * 租户编号 | ||
| 25 | + */ | ||
| 26 | + private Long tenantId; | ||
| 27 | + /** | ||
| 28 | + * 授权范围 | ||
| 29 | + */ | ||
| 30 | + private List<String> scopes; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 访问令牌 | ||
| 34 | + */ | ||
| 35 | + private String accessToken; | ||
| 36 | + | ||
| 37 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.filter; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.OAuth2Client; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO; | ||
| 6 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | ||
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 8 | +import org.springframework.stereotype.Component; | ||
| 9 | +import org.springframework.util.StringUtils; | ||
| 10 | +import org.springframework.web.filter.OncePerRequestFilter; | ||
| 11 | + | ||
| 12 | +import javax.annotation.Resource; | ||
| 13 | +import javax.servlet.FilterChain; | ||
| 14 | +import javax.servlet.ServletException; | ||
| 15 | +import javax.servlet.http.HttpServletRequest; | ||
| 16 | +import javax.servlet.http.HttpServletResponse; | ||
| 17 | +import java.io.IOException; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Token 过滤器,验证 token 的有效性 | ||
| 21 | + * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 | ||
| 22 | + * | ||
| 23 | + * @author 芋道源码 | ||
| 24 | + */ | ||
| 25 | +@Component | ||
| 26 | +public class TokenAuthenticationFilter extends OncePerRequestFilter { | ||
| 27 | + | ||
| 28 | + @Resource | ||
| 29 | + private OAuth2Client oauth2Client; | ||
| 30 | + | ||
| 31 | + @Override | ||
| 32 | + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | ||
| 33 | + FilterChain filterChain) throws ServletException, IOException { | ||
| 34 | + // 1. 获得访问令牌 | ||
| 35 | + String token = SecurityUtils.obtainAuthorization(request, "Authorization"); | ||
| 36 | + if (StringUtils.hasText(token)) { | ||
| 37 | + // 2. 基于 token 构建登录用户 | ||
| 38 | + LoginUser loginUser = buildLoginUserByToken(token); | ||
| 39 | + // 3. 设置当前用户 | ||
| 40 | + if (loginUser != null) { | ||
| 41 | + SecurityUtils.setLoginUser(loginUser, request); | ||
| 42 | + } | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + // 继续过滤链 | ||
| 46 | + filterChain.doFilter(request, response); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + private LoginUser buildLoginUserByToken(String token) { | ||
| 50 | + try { | ||
| 51 | + CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token); | ||
| 52 | + OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData(); | ||
| 53 | + if (accessToken == null) { | ||
| 54 | + return null; | ||
| 55 | + } | ||
| 56 | + // 构建登录用户 | ||
| 57 | + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) | ||
| 58 | + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()) | ||
| 59 | + .setAccessToken(accessToken.getAccessToken()); | ||
| 60 | + } catch (Exception exception) { | ||
| 61 | + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 | ||
| 62 | + return null; | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.handler; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 5 | +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils; | ||
| 6 | +import lombok.extern.slf4j.Slf4j; | ||
| 7 | +import org.springframework.http.HttpStatus; | ||
| 8 | +import org.springframework.security.access.AccessDeniedException; | ||
| 9 | +import org.springframework.security.web.access.AccessDeniedHandler; | ||
| 10 | +import org.springframework.security.web.access.ExceptionTranslationFilter; | ||
| 11 | +import org.springframework.stereotype.Component; | ||
| 12 | + | ||
| 13 | +import javax.servlet.FilterChain; | ||
| 14 | +import javax.servlet.ServletException; | ||
| 15 | +import javax.servlet.http.HttpServletRequest; | ||
| 16 | +import javax.servlet.http.HttpServletResponse; | ||
| 17 | +import java.io.IOException; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。 | ||
| 21 | + * | ||
| 22 | + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类 | ||
| 23 | + * | ||
| 24 | + * @author 芋道源码 | ||
| 25 | + */ | ||
| 26 | +@Component | ||
| 27 | +@SuppressWarnings("JavadocReference") | ||
| 28 | +@Slf4j | ||
| 29 | +public class AccessDeniedHandlerImpl implements AccessDeniedHandler { | ||
| 30 | + | ||
| 31 | + @Override | ||
| 32 | + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) | ||
| 33 | + throws IOException, ServletException { | ||
| 34 | + // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏 | ||
| 35 | + log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(), | ||
| 36 | + SecurityUtils.getLoginUserId(), e); | ||
| 37 | + // 返回 403 | ||
| 38 | + CommonResult<Object> result = new CommonResult<>(); | ||
| 39 | + result.setCode(HttpStatus.FORBIDDEN.value()); | ||
| 40 | + result.setMsg("没有该操作权限"); | ||
| 41 | + ServletUtils.writeJSON(response, result); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.handler; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils; | ||
| 5 | +import lombok.extern.slf4j.Slf4j; | ||
| 6 | +import org.springframework.http.HttpStatus; | ||
| 7 | +import org.springframework.security.core.AuthenticationException; | ||
| 8 | +import org.springframework.security.web.AuthenticationEntryPoint; | ||
| 9 | +import org.springframework.security.web.access.ExceptionTranslationFilter; | ||
| 10 | +import org.springframework.stereotype.Component; | ||
| 11 | + | ||
| 12 | +import javax.servlet.FilterChain; | ||
| 13 | +import javax.servlet.http.HttpServletRequest; | ||
| 14 | +import javax.servlet.http.HttpServletResponse; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页 | ||
| 18 | + * | ||
| 19 | + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类 | ||
| 20 | + */ | ||
| 21 | +@Component | ||
| 22 | +@Slf4j | ||
| 23 | +@SuppressWarnings("JavadocReference") // 忽略文档引用报错 | ||
| 24 | +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { | ||
| 25 | + | ||
| 26 | + @Override | ||
| 27 | + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { | ||
| 28 | + log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e); | ||
| 29 | + // 返回 401 | ||
| 30 | + CommonResult<Object> result = new CommonResult<>(); | ||
| 31 | + result.setCode(HttpStatus.UNAUTHORIZED.value()); | ||
| 32 | + result.setMsg("账号未登录"); | ||
| 33 | + ServletUtils.writeJSON(response, result); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.util; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | ||
| 4 | +import org.springframework.lang.Nullable; | ||
| 5 | +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
| 6 | +import org.springframework.security.core.Authentication; | ||
| 7 | +import org.springframework.security.core.context.SecurityContext; | ||
| 8 | +import org.springframework.security.core.context.SecurityContextHolder; | ||
| 9 | +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
| 10 | +import org.springframework.util.StringUtils; | ||
| 11 | + | ||
| 12 | +import javax.servlet.http.HttpServletRequest; | ||
| 13 | +import java.util.Collections; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * 安全服务工具类 | ||
| 17 | + * | ||
| 18 | + * @author 芋道源码 | ||
| 19 | + */ | ||
| 20 | +public class SecurityUtils { | ||
| 21 | + | ||
| 22 | + public static final String AUTHORIZATION_BEARER = "Bearer"; | ||
| 23 | + | ||
| 24 | + private SecurityUtils() {} | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 从请求中,获得认证 Token | ||
| 28 | + * | ||
| 29 | + * @param request 请求 | ||
| 30 | + * @param header 认证 Token 对应的 Header 名字 | ||
| 31 | + * @return 认证 Token | ||
| 32 | + */ | ||
| 33 | + public static String obtainAuthorization(HttpServletRequest request, String header) { | ||
| 34 | + String authorization = request.getHeader(header); | ||
| 35 | + if (!StringUtils.hasText(authorization)) { | ||
| 36 | + return null; | ||
| 37 | + } | ||
| 38 | + int index = authorization.indexOf(AUTHORIZATION_BEARER + " "); | ||
| 39 | + if (index == -1) { // 未找到 | ||
| 40 | + return null; | ||
| 41 | + } | ||
| 42 | + return authorization.substring(index + 7).trim(); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 获得当前认证信息 | ||
| 47 | + * | ||
| 48 | + * @return 认证信息 | ||
| 49 | + */ | ||
| 50 | + public static Authentication getAuthentication() { | ||
| 51 | + SecurityContext context = SecurityContextHolder.getContext(); | ||
| 52 | + if (context == null) { | ||
| 53 | + return null; | ||
| 54 | + } | ||
| 55 | + return context.getAuthentication(); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + /** | ||
| 59 | + * 获取当前用户 | ||
| 60 | + * | ||
| 61 | + * @return 当前用户 | ||
| 62 | + */ | ||
| 63 | + @Nullable | ||
| 64 | + public static LoginUser getLoginUser() { | ||
| 65 | + Authentication authentication = getAuthentication(); | ||
| 66 | + if (authentication == null) { | ||
| 67 | + return null; | ||
| 68 | + } | ||
| 69 | + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * 获得当前用户的编号,从上下文中 | ||
| 74 | + * | ||
| 75 | + * @return 用户编号 | ||
| 76 | + */ | ||
| 77 | + @Nullable | ||
| 78 | + public static Long getLoginUserId() { | ||
| 79 | + LoginUser loginUser = getLoginUser(); | ||
| 80 | + return loginUser != null ? loginUser.getId() : null; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + /** | ||
| 84 | + * 设置当前用户 | ||
| 85 | + * | ||
| 86 | + * @param loginUser 登录用户 | ||
| 87 | + * @param request 请求 | ||
| 88 | + */ | ||
| 89 | + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { | ||
| 90 | + // 创建 Authentication,并设置到上下文 | ||
| 91 | + Authentication authentication = buildAuthentication(loginUser, request); | ||
| 92 | + SecurityContextHolder.getContext().setAuthentication(authentication); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { | ||
| 96 | + // 创建 UsernamePasswordAuthenticationToken 对象 | ||
| 97 | + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( | ||
| 98 | + loginUser, null, Collections.emptyList()); | ||
| 99 | + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
| 100 | + return authenticationToken; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.util; | ||
| 2 | + | ||
| 3 | +import cn.hutool.extra.servlet.ServletUtil; | ||
| 4 | +import cn.hutool.json.JSONUtil; | ||
| 5 | +import org.springframework.http.MediaType; | ||
| 6 | + | ||
| 7 | +import javax.servlet.http.HttpServletResponse; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 客户端工具类 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +public class ServletUtils { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 返回 JSON 字符串 | ||
| 18 | + * | ||
| 19 | + * @param response 响应 | ||
| 20 | + * @param object 对象,会序列化成 JSON 字符串 | ||
| 21 | + */ | ||
| 22 | + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 | ||
| 23 | + public static void writeJSON(HttpServletResponse response, Object object) { | ||
| 24 | + String content = JSONUtil.toJsonStr(object); | ||
| 25 | + ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | +} |
yudao-example/yudao-sso-demo-by-password/src/main/resources/application.yaml
0 → 100644
yudao-example/yudao-sso-demo-by-password/src/main/resources/static/index.html
0 → 100644
| 1 | +<!DOCTYPE html> | ||
| 2 | +<html lang="en"> | ||
| 3 | +<head> | ||
| 4 | + <meta charset="UTF-8"> | ||
| 5 | + <title>首页</title> | ||
| 6 | + <!-- jQuery:操作 dom、发起请求等 --> | ||
| 7 | + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> | ||
| 8 | + | ||
| 9 | + <script type="application/javascript"> | ||
| 10 | + | ||
| 11 | + /** | ||
| 12 | + * 跳转单点登录 | ||
| 13 | + */ | ||
| 14 | + function passwordLogin() { | ||
| 15 | + window.location.href = '/login.html' | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 修改昵称 | ||
| 20 | + */ | ||
| 21 | + function updateNickname() { | ||
| 22 | + const nickname = prompt("请输入新的昵称", ""); | ||
| 23 | + if (!nickname) { | ||
| 24 | + return; | ||
| 25 | + } | ||
| 26 | + // 更新用户的昵称 | ||
| 27 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||
| 28 | + $.ajax({ | ||
| 29 | + url: "http://127.0.0.1:18080/user/update?nickname=" + nickname, | ||
| 30 | + method: 'PUT', | ||
| 31 | + headers: { | ||
| 32 | + 'Authorization': 'Bearer ' + accessToken | ||
| 33 | + }, | ||
| 34 | + success: function (result) { | ||
| 35 | + if (result.code !== 0) { | ||
| 36 | + alert('更新昵称失败,原因:' + result.msg) | ||
| 37 | + return; | ||
| 38 | + } | ||
| 39 | + alert('更新昵称成功!'); | ||
| 40 | + $('#nicknameSpan').html(nickname); | ||
| 41 | + } | ||
| 42 | + }); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 刷新令牌 | ||
| 47 | + */ | ||
| 48 | + function refreshToken() { | ||
| 49 | + const refreshToken = localStorage.getItem('REFRESH-TOKEN'); | ||
| 50 | + if (!refreshToken) { | ||
| 51 | + alert("获取不到刷新令牌"); | ||
| 52 | + return; | ||
| 53 | + } | ||
| 54 | + $.ajax({ | ||
| 55 | + url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken, | ||
| 56 | + method: 'POST', | ||
| 57 | + success: function (result) { | ||
| 58 | + if (result.code !== 0) { | ||
| 59 | + alert('刷新访问令牌失败,原因:' + result.msg) | ||
| 60 | + return; | ||
| 61 | + } | ||
| 62 | + alert('更新访问令牌成功!'); | ||
| 63 | + $('#accessTokenSpan').html(result.data.access_token); | ||
| 64 | + | ||
| 65 | + // 设置到 localStorage 中 | ||
| 66 | + localStorage.setItem('ACCESS-TOKEN', result.data.access_token); | ||
| 67 | + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token); | ||
| 68 | + } | ||
| 69 | + }); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * 登出,删除访问令牌 | ||
| 74 | + */ | ||
| 75 | + function logout() { | ||
| 76 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||
| 77 | + if (!accessToken) { | ||
| 78 | + location.reload(); | ||
| 79 | + return; | ||
| 80 | + } | ||
| 81 | + $.ajax({ | ||
| 82 | + url: "http://127.0.0.1:18080/auth/logout", | ||
| 83 | + method: 'POST', | ||
| 84 | + headers: { | ||
| 85 | + 'Authorization': 'Bearer ' + accessToken | ||
| 86 | + }, | ||
| 87 | + success: function (result) { | ||
| 88 | + if (result.code !== 0) { | ||
| 89 | + alert('退出登录失败,原因:' + result.msg) | ||
| 90 | + return; | ||
| 91 | + } | ||
| 92 | + alert('退出登录成功!'); | ||
| 93 | + // 删除 localStorage 中 | ||
| 94 | + localStorage.removeItem('ACCESS-TOKEN'); | ||
| 95 | + localStorage.removeItem('REFRESH-TOKEN'); | ||
| 96 | + | ||
| 97 | + location.reload(); | ||
| 98 | + } | ||
| 99 | + }); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + $(function () { | ||
| 103 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||
| 104 | + // 情况一:未登录 | ||
| 105 | + if (!accessToken) { | ||
| 106 | + $('#noLoginDiv').css("display", "block"); | ||
| 107 | + return; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + // 情况二:已登录 | ||
| 111 | + $('#yesLoginDiv').css("display", "block"); | ||
| 112 | + $('#accessTokenSpan').html(accessToken); | ||
| 113 | + // 获得登录用户的信息 | ||
| 114 | + $.ajax({ | ||
| 115 | + url: "http://127.0.0.1:18080/user/get", | ||
| 116 | + method: 'GET', | ||
| 117 | + headers: { | ||
| 118 | + 'Authorization': 'Bearer ' + accessToken | ||
| 119 | + }, | ||
| 120 | + success: function (result) { | ||
| 121 | + if (result.code !== 0) { | ||
| 122 | + alert('获得个人信息失败,原因:' + result.msg) | ||
| 123 | + return; | ||
| 124 | + } | ||
| 125 | + $('#nicknameSpan').html(result.data.nickname); | ||
| 126 | + } | ||
| 127 | + }); | ||
| 128 | + }) | ||
| 129 | + </script> | ||
| 130 | +</head> | ||
| 131 | +<body> | ||
| 132 | +<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 --> | ||
| 133 | +<div id="noLoginDiv" style="display: none"> | ||
| 134 | + 您未登录,点击 <a href="#" onclick="passwordLogin()">跳转 </a> 账号密码登录 | ||
| 135 | +</div> | ||
| 136 | + | ||
| 137 | +<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> | ||
| 138 | +<div id="yesLoginDiv" style="display: none"> | ||
| 139 | + 您已登录!<button onclick="logout()">退出登录</button> <br /> | ||
| 140 | + 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br /> | ||
| 141 | + 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br /> | ||
| 142 | +</div> | ||
| 143 | +</body> | ||
| 144 | +<style> | ||
| 145 | + body { /** 页面居中 */ | ||
| 146 | + border-radius: 20px; | ||
| 147 | + height: 350px; | ||
| 148 | + position: absolute; | ||
| 149 | + left: 50%; | ||
| 150 | + top: 50%; | ||
| 151 | + transform: translate(-50%,-50%); | ||
| 152 | + } | ||
| 153 | +</style> | ||
| 154 | +</html> |
yudao-example/yudao-sso-demo-by-password/src/main/resources/static/login.html
0 → 100644
| 1 | +<!DOCTYPE html> | ||
| 2 | +<html lang="en"> | ||
| 3 | +<head> | ||
| 4 | + <meta charset="UTF-8"> | ||
| 5 | + <title>登录</title> | ||
| 6 | + <!-- jQuery:操作 dom、发起请求等 --> | ||
| 7 | + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> | ||
| 8 | + | ||
| 9 | + <script type="application/javascript"> | ||
| 10 | + | ||
| 11 | + /** | ||
| 12 | + * 账号密码登录 | ||
| 13 | + */ | ||
| 14 | + function login() { | ||
| 15 | + const clientId = 'yudao-sso-demo-by-password'; // 可以改写成,你的 clientId | ||
| 16 | + const clientSecret = 'test'; // 可以改写成,你的 clientSecret | ||
| 17 | + const grantType = 'password'; // 密码模式 | ||
| 18 | + | ||
| 19 | + // 账号 + 密码 | ||
| 20 | + const username = $('#username').val(); | ||
| 21 | + const password = $('#password').val(); | ||
| 22 | + if (username.length === 0 || password.length === 0) { | ||
| 23 | + alert('账号或密码未输入'); | ||
| 24 | + return; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + // 发起请求 | ||
| 28 | + $.ajax({ | ||
| 29 | + url: "http://127.0.0.1:48080/admin-api/system/oauth2/token?" | ||
| 30 | + // 客户端 | ||
| 31 | + + "client_id=" + clientId | ||
| 32 | + + "&client_secret=" + clientSecret | ||
| 33 | + // 密码模式的参数 | ||
| 34 | + + "&grant_type=" + grantType | ||
| 35 | + + "&username=" + username | ||
| 36 | + + "&password=" + password | ||
| 37 | + + '&scope=user.read user.write', | ||
| 38 | + method: 'POST', | ||
| 39 | + headers: { | ||
| 40 | + 'tenant-id': '1', // 多租户编号,写死 | ||
| 41 | + }, | ||
| 42 | + success: function (result) { | ||
| 43 | + if (result.code !== 0) { | ||
| 44 | + alert('登录失败,原因:' + result.msg) | ||
| 45 | + return; | ||
| 46 | + } | ||
| 47 | + // 设置到 localStorage 中 | ||
| 48 | + localStorage.setItem('ACCESS-TOKEN', result.data.access_token); | ||
| 49 | + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token); | ||
| 50 | + | ||
| 51 | + // 提示登录成功 | ||
| 52 | + alert('登录成功!点击确认,跳转回首页'); | ||
| 53 | + window.location.href = '/index.html'; | ||
| 54 | + } | ||
| 55 | + }); | ||
| 56 | + } | ||
| 57 | + </script> | ||
| 58 | +</head> | ||
| 59 | +<body> | ||
| 60 | +账号:<input id="username" value="admin" /> <br /> | ||
| 61 | +密码:<input id="password" value="admin123" > <br /> | ||
| 62 | +<button style="float: right; margin-top: 5px;" onclick="login()">登录</button> | ||
| 63 | +</body> | ||
| 64 | +<style> | ||
| 65 | + body { /** 页面居中 */ | ||
| 66 | + border-radius: 20px; | ||
| 67 | + height: 350px; | ||
| 68 | + position: absolute; | ||
| 69 | + left: 50%; | ||
| 70 | + top: 50%; | ||
| 71 | + transform: translate(-50%,-50%); | ||
| 72 | + } | ||
| 73 | +</style> | ||
| 74 | +</html> |
yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml
| @@ -52,7 +52,7 @@ | @@ -52,7 +52,7 @@ | ||
| 52 | <dependency> | 52 | <dependency> |
| 53 | <groupId>com.alipay.sdk</groupId> | 53 | <groupId>com.alipay.sdk</groupId> |
| 54 | <artifactId>alipay-sdk-java</artifactId> | 54 | <artifactId>alipay-sdk-java</artifactId> |
| 55 | - <version>4.31.72.ALL</version> | 55 | + <version>4.33.44.ALL</version> |
| 56 | <exclusions> | 56 | <exclusions> |
| 57 | <exclusion> | 57 | <exclusion> |
| 58 | <groupId>org.bouncycastle</groupId> | 58 | <groupId>org.bouncycastle</groupId> |
| @@ -63,7 +63,7 @@ | @@ -63,7 +63,7 @@ | ||
| 63 | <dependency> | 63 | <dependency> |
| 64 | <groupId>com.github.binarywang</groupId> | 64 | <groupId>com.github.binarywang</groupId> |
| 65 | <artifactId>weixin-java-pay</artifactId> | 65 | <artifactId>weixin-java-pay</artifactId> |
| 66 | - <version>4.3.8.B</version> | 66 | + <version>4.4.0</version> |
| 67 | </dependency> | 67 | </dependency> |
| 68 | <!-- TODO 芋艿:清理 --> | 68 | <!-- TODO 芋艿:清理 --> |
| 69 | 69 |
yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml
| @@ -35,12 +35,12 @@ | @@ -35,12 +35,12 @@ | ||
| 35 | <dependency> | 35 | <dependency> |
| 36 | <groupId>com.github.binarywang</groupId> | 36 | <groupId>com.github.binarywang</groupId> |
| 37 | <artifactId>wx-java-mp-spring-boot-starter</artifactId> | 37 | <artifactId>wx-java-mp-spring-boot-starter</artifactId> |
| 38 | - <version>4.3.8.B</version> | 38 | + <version>4.4.0</version> |
| 39 | </dependency> | 39 | </dependency> |
| 40 | <dependency> | 40 | <dependency> |
| 41 | <groupId>com.github.binarywang</groupId> | 41 | <groupId>com.github.binarywang</groupId> |
| 42 | <artifactId>wx-java-miniapp-spring-boot-starter</artifactId> | 42 | <artifactId>wx-java-miniapp-spring-boot-starter</artifactId> |
| 43 | - <version>4.3.8.B</version> | 43 | + <version>4.4.0</version> |
| 44 | </dependency> | 44 | </dependency> |
| 45 | <!-- TODO 芋艿:清理 --> | 45 | <!-- TODO 芋艿:清理 --> |
| 46 | </dependencies> | 46 | </dependencies> |
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/lock4j/core/DefaultLockFailureStrategy.java
| @@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant | @@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant | ||
| 5 | import com.baomidou.lock.LockFailureStrategy; | 5 | import com.baomidou.lock.LockFailureStrategy; |
| 6 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| 7 | 7 | ||
| 8 | +import java.lang.reflect.Method; | ||
| 9 | + | ||
| 8 | /** | 10 | /** |
| 9 | * 自定义获取锁失败策略,抛出 {@link ServiceException} 异常 | 11 | * 自定义获取锁失败策略,抛出 {@link ServiceException} 异常 |
| 10 | */ | 12 | */ |
| @@ -12,9 +14,8 @@ import lombok.extern.slf4j.Slf4j; | @@ -12,9 +14,8 @@ import lombok.extern.slf4j.Slf4j; | ||
| 12 | public class DefaultLockFailureStrategy implements LockFailureStrategy { | 14 | public class DefaultLockFailureStrategy implements LockFailureStrategy { |
| 13 | 15 | ||
| 14 | @Override | 16 | @Override |
| 15 | - public void onLockFailure(String key, long acquireTimeout, int acquireCount) { | ||
| 16 | - log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取超时时长:{} ms]", Thread.currentThread().getName(), key, acquireTimeout); | 17 | + public void onLockFailure(String key, Method method, Object[] arguments) { |
| 18 | + log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取失败:{} ]", Thread.currentThread().getName(), key, arguments); | ||
| 17 | throw new ServiceException(GlobalErrorCodeConstants.LOCKED); | 19 | throw new ServiceException(GlobalErrorCodeConstants.LOCKED); |
| 18 | } | 20 | } |
| 19 | - | ||
| 20 | } | 21 | } |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/message/BpmMessageConvert.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.convert.message; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.convert.message; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO; | 3 | import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO; |
| 4 | import org.mapstruct.Mapper; | 4 | import org.mapstruct.Mapper; |
| 5 | +import org.mapstruct.Mapping; | ||
| 5 | import org.mapstruct.factory.Mappers; | 6 | import org.mapstruct.factory.Mappers; |
| 6 | 7 | ||
| 7 | import java.util.Map; | 8 | import java.util.Map; |
| @@ -11,6 +12,10 @@ public interface BpmMessageConvert { | @@ -11,6 +12,10 @@ public interface BpmMessageConvert { | ||
| 11 | 12 | ||
| 12 | BpmMessageConvert INSTANCE = Mappers.getMapper(BpmMessageConvert.class); | 13 | BpmMessageConvert INSTANCE = Mappers.getMapper(BpmMessageConvert.class); |
| 13 | 14 | ||
| 15 | + @Mapping(target = "mobile", ignore = true) | ||
| 16 | + @Mapping(source = "userId", target = "userId") | ||
| 17 | + @Mapping(source = "templateCode", target = "templateCode") | ||
| 18 | + @Mapping(source = "templateParams", target = "templateParams") | ||
| 14 | SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map<String, Object> templateParams); | 19 | SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map<String, Object> templateParams); |
| 15 | 20 | ||
| 16 | } | 21 | } |
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
| @@ -50,23 +50,29 @@ | @@ -50,23 +50,29 @@ | ||
| 50 | 50 | ||
| 51 | // 提交按钮 | 51 | // 提交按钮 |
| 52 | const submitForm = async () => { | 52 | const submitForm = async () => { |
| 53 | - actionLoading.value = true | ||
| 54 | - // 提交请求 | ||
| 55 | - try { | ||
| 56 | - const data = unref(formRef)?.formModel as ${simpleClassName}VO | ||
| 57 | - if (actionType.value === 'create') { | ||
| 58 | - await ${simpleClassName}Api.create${simpleClassName}Api(data) | ||
| 59 | - ElMessage.success(t('common.createSuccess')) | ||
| 60 | - } else { | ||
| 61 | - await ${simpleClassName}Api.update${simpleClassName}Api(data) | ||
| 62 | - ElMessage.success(t('common.updateSuccess')) | 53 | + const elForm = unref(formRef)?.getElFormRef() |
| 54 | + if (!elForm) return | ||
| 55 | + elForm.validate(async (valid) => { | ||
| 56 | + if (valid) { | ||
| 57 | + actionLoading.value = true | ||
| 58 | + // 提交请求 | ||
| 59 | + try { | ||
| 60 | + const data = unref(formRef)?.formModel as ${simpleClassName}VO | ||
| 61 | + if (actionType.value === 'create') { | ||
| 62 | + await ${simpleClassName}Api.create${simpleClassName}Api(data) | ||
| 63 | + ElMessage.success(t('common.createSuccess')) | ||
| 64 | + } else { | ||
| 65 | + await ${simpleClassName}Api.update${simpleClassName}Api(data) | ||
| 66 | + ElMessage.success(t('common.updateSuccess')) | ||
| 67 | + } | ||
| 68 | + // 操作成功,重新加载列表 | ||
| 69 | + dialogVisible.value = false | ||
| 70 | + await getList() | ||
| 71 | + } finally { | ||
| 72 | + actionLoading.value = false | ||
| 73 | + } | ||
| 63 | } | 74 | } |
| 64 | - // 操作成功,重新加载列表 | ||
| 65 | - dialogVisible.value = false | ||
| 66 | - await getList() | ||
| 67 | - } finally { | ||
| 68 | - actionLoading.value = false | ||
| 69 | - } | 75 | + }) |
| 70 | } | 76 | } |
| 71 | 77 | ||
| 72 | // ========== 详情相关 ========== | 78 | // ========== 详情相关 ========== |
yudao-module-mall/yudao-module-coupon-biz/pom.xml
| @@ -30,10 +30,6 @@ | @@ -30,10 +30,6 @@ | ||
| 30 | <groupId>cn.iocoder.boot</groupId> | 30 | <groupId>cn.iocoder.boot</groupId> |
| 31 | <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> | 31 | <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> |
| 32 | </dependency> | 32 | </dependency> |
| 33 | - <dependency> | ||
| 34 | - <groupId>cn.iocoder.boot</groupId> | ||
| 35 | - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | ||
| 36 | - </dependency> | ||
| 37 | 33 | ||
| 38 | <!-- Web 相关 --> | 34 | <!-- Web 相关 --> |
| 39 | <dependency> | 35 | <dependency> |
| @@ -42,7 +38,7 @@ | @@ -42,7 +38,7 @@ | ||
| 42 | </dependency> | 38 | </dependency> |
| 43 | <dependency> | 39 | <dependency> |
| 44 | <groupId>cn.iocoder.boot</groupId> | 40 | <groupId>cn.iocoder.boot</groupId> |
| 45 | - <artifactId>yudao-spring-boot-starter-excel</artifactId> | 41 | + <artifactId>yudao-spring-boot-starter-security</artifactId> |
| 46 | </dependency> | 42 | </dependency> |
| 47 | 43 | ||
| 48 | <!-- DB 相关 --> | 44 | <!-- DB 相关 --> |
| @@ -57,6 +53,12 @@ | @@ -57,6 +53,12 @@ | ||
| 57 | <artifactId>yudao-spring-boot-starter-test</artifactId> | 53 | <artifactId>yudao-spring-boot-starter-test</artifactId> |
| 58 | </dependency> | 54 | </dependency> |
| 59 | 55 | ||
| 56 | + <!-- 工具类相关 --> | ||
| 57 | + <dependency> | ||
| 58 | + <groupId>cn.iocoder.boot</groupId> | ||
| 59 | + <artifactId>yudao-spring-boot-starter-excel</artifactId> | ||
| 60 | + </dependency> | ||
| 61 | + | ||
| 60 | </dependencies> | 62 | </dependencies> |
| 61 | 63 | ||
| 62 | -</project> | ||
| 63 | \ No newline at end of file | 64 | \ No newline at end of file |
| 65 | +</project> |
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/service/coupon/CouponServiceImpl.java
| @@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil; | @@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil; | ||
| 4 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | 4 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| 5 | import cn.iocoder.yudao.module.coupon.dal.dataobject.CouponTemplete.CouponTempleteDO; | 5 | import cn.iocoder.yudao.module.coupon.dal.dataobject.CouponTemplete.CouponTempleteDO; |
| 6 | import cn.iocoder.yudao.module.coupon.dal.mysql.CouponTemplete.CouponTempleteMapper; | 6 | import cn.iocoder.yudao.module.coupon.dal.mysql.CouponTemplete.CouponTempleteMapper; |
| 7 | -import io.micrometer.core.instrument.Counter; | ||
| 8 | import org.springframework.stereotype.Service; | 7 | import org.springframework.stereotype.Service; |
| 9 | import javax.annotation.Resource; | 8 | import javax.annotation.Resource; |
| 10 | import org.springframework.validation.annotation.Validated; | 9 | import org.springframework.validation.annotation.Validated; |
yudao-module-mall/yudao-module-market-biz/pom.xml
| @@ -39,16 +39,16 @@ | @@ -39,16 +39,16 @@ | ||
| 39 | <groupId>cn.iocoder.boot</groupId> | 39 | <groupId>cn.iocoder.boot</groupId> |
| 40 | <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId> | 40 | <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId> |
| 41 | </dependency> | 41 | </dependency> |
| 42 | - <dependency> | ||
| 43 | - <groupId>cn.iocoder.boot</groupId> | ||
| 44 | - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | ||
| 45 | - </dependency> | ||
| 46 | 42 | ||
| 47 | <!-- Web 相关 --> | 43 | <!-- Web 相关 --> |
| 48 | <dependency> | 44 | <dependency> |
| 49 | <groupId>cn.iocoder.boot</groupId> | 45 | <groupId>cn.iocoder.boot</groupId> |
| 50 | <artifactId>yudao-spring-boot-starter-web</artifactId> | 46 | <artifactId>yudao-spring-boot-starter-web</artifactId> |
| 51 | </dependency> | 47 | </dependency> |
| 48 | + <dependency> | ||
| 49 | + <groupId>cn.iocoder.boot</groupId> | ||
| 50 | + <artifactId>yudao-spring-boot-starter-security</artifactId> | ||
| 51 | + </dependency> | ||
| 52 | 52 | ||
| 53 | <!-- DB 相关 --> | 53 | <!-- DB 相关 --> |
| 54 | <dependency> | 54 | <dependency> |
| @@ -61,6 +61,12 @@ | @@ -61,6 +61,12 @@ | ||
| 61 | <groupId>cn.iocoder.boot</groupId> | 61 | <groupId>cn.iocoder.boot</groupId> |
| 62 | <artifactId>yudao-spring-boot-starter-test</artifactId> | 62 | <artifactId>yudao-spring-boot-starter-test</artifactId> |
| 63 | </dependency> | 63 | </dependency> |
| 64 | + | ||
| 65 | + <!-- 工具类相关 --> | ||
| 66 | + <dependency> | ||
| 67 | + <groupId>cn.iocoder.boot</groupId> | ||
| 68 | + <artifactId>yudao-spring-boot-starter-excel</artifactId> | ||
| 69 | + </dependency> | ||
| 64 | </dependencies> | 70 | </dependencies> |
| 65 | 71 | ||
| 66 | </project> | 72 | </project> |
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/controller/app/banner/AppBannerController.java
| @@ -7,13 +7,13 @@ import cn.iocoder.yudao.module.market.dal.dataobject.banner.BannerDO; | @@ -7,13 +7,13 @@ import cn.iocoder.yudao.module.market.dal.dataobject.banner.BannerDO; | ||
| 7 | import cn.iocoder.yudao.module.market.service.banner.BannerService; | 7 | import cn.iocoder.yudao.module.market.service.banner.BannerService; |
| 8 | import io.swagger.annotations.Api; | 8 | import io.swagger.annotations.Api; |
| 9 | import io.swagger.annotations.ApiOperation; | 9 | import io.swagger.annotations.ApiOperation; |
| 10 | -import org.springframework.beans.factory.annotation.Autowired; | ||
| 11 | import org.springframework.security.access.prepost.PreAuthorize; | 10 | import org.springframework.security.access.prepost.PreAuthorize; |
| 12 | import org.springframework.validation.annotation.Validated; | 11 | import org.springframework.validation.annotation.Validated; |
| 13 | import org.springframework.web.bind.annotation.GetMapping; | 12 | import org.springframework.web.bind.annotation.GetMapping; |
| 14 | import org.springframework.web.bind.annotation.RequestMapping; | 13 | import org.springframework.web.bind.annotation.RequestMapping; |
| 15 | import org.springframework.web.bind.annotation.RestController; | 14 | import org.springframework.web.bind.annotation.RestController; |
| 16 | 15 | ||
| 16 | +import javax.annotation.Resource; | ||
| 17 | import java.util.List; | 17 | import java.util.List; |
| 18 | 18 | ||
| 19 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 19 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| @@ -27,8 +27,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | @@ -27,8 +27,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||
| 27 | @Validated | 27 | @Validated |
| 28 | public class AppBannerController { | 28 | public class AppBannerController { |
| 29 | 29 | ||
| 30 | - // TODO @xia:使用 @Resource 哈 | ||
| 31 | - @Autowired | 30 | + @Resource |
| 32 | private BannerService bannerService; | 31 | private BannerService bannerService; |
| 33 | 32 | ||
| 34 | // TODO @xia:新建一个 AppBannerRespVO,只返回必要的字段。status 要过滤下。然后 sort 下结果 | 33 | // TODO @xia:新建一个 AppBannerRespVO,只返回必要的字段。status 要过滤下。然后 sort 下结果 |
yudao-module-mall/yudao-module-product-biz/pom.xml
| @@ -29,10 +29,6 @@ | @@ -29,10 +29,6 @@ | ||
| 29 | <groupId>cn.iocoder.boot</groupId> | 29 | <groupId>cn.iocoder.boot</groupId> |
| 30 | <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> | 30 | <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> |
| 31 | </dependency> | 31 | </dependency> |
| 32 | - <dependency> | ||
| 33 | - <groupId>cn.iocoder.boot</groupId> | ||
| 34 | - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | ||
| 35 | - </dependency> | ||
| 36 | 32 | ||
| 37 | <!-- Web 相关 --> | 33 | <!-- Web 相关 --> |
| 38 | <dependency> | 34 | <dependency> |
| @@ -41,7 +37,7 @@ | @@ -41,7 +37,7 @@ | ||
| 41 | </dependency> | 37 | </dependency> |
| 42 | <dependency> | 38 | <dependency> |
| 43 | <groupId>cn.iocoder.boot</groupId> | 39 | <groupId>cn.iocoder.boot</groupId> |
| 44 | - <artifactId>yudao-spring-boot-starter-excel</artifactId> | 40 | + <artifactId>yudao-spring-boot-starter-security</artifactId> |
| 45 | </dependency> | 41 | </dependency> |
| 46 | 42 | ||
| 47 | <!-- DB 相关 --> | 43 | <!-- DB 相关 --> |
| @@ -56,6 +52,11 @@ | @@ -56,6 +52,11 @@ | ||
| 56 | <artifactId>yudao-spring-boot-starter-test</artifactId> | 52 | <artifactId>yudao-spring-boot-starter-test</artifactId> |
| 57 | </dependency> | 53 | </dependency> |
| 58 | 54 | ||
| 55 | + <!-- 工具类相关 --> | ||
| 56 | + <dependency> | ||
| 57 | + <groupId>cn.iocoder.boot</groupId> | ||
| 58 | + <artifactId>yudao-spring-boot-starter-excel</artifactId> | ||
| 59 | + </dependency> | ||
| 59 | </dependencies> | 60 | </dependencies> |
| 60 | 61 | ||
| 61 | </project> | 62 | </project> |
yudao-module-mall/yudao-module-trade-biz/pom.xml
| @@ -47,10 +47,6 @@ | @@ -47,10 +47,6 @@ | ||
| 47 | <groupId>cn.iocoder.boot</groupId> | 47 | <groupId>cn.iocoder.boot</groupId> |
| 48 | <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> | 48 | <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> |
| 49 | </dependency> | 49 | </dependency> |
| 50 | - <dependency> | ||
| 51 | - <groupId>cn.iocoder.boot</groupId> | ||
| 52 | - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | ||
| 53 | - </dependency> | ||
| 54 | 50 | ||
| 55 | <!-- Web 相关 --> | 51 | <!-- Web 相关 --> |
| 56 | <dependency> | 52 | <dependency> |
| @@ -60,13 +56,13 @@ | @@ -60,13 +56,13 @@ | ||
| 60 | 56 | ||
| 61 | <dependency> | 57 | <dependency> |
| 62 | <groupId>cn.iocoder.boot</groupId> | 58 | <groupId>cn.iocoder.boot</groupId> |
| 63 | - <artifactId>yudao-spring-boot-starter-biz-pay</artifactId> | 59 | + <artifactId>yudao-spring-boot-starter-security</artifactId> |
| 64 | </dependency> | 60 | </dependency> |
| 65 | 61 | ||
| 66 | - <!-- DB 相关 --> | 62 | + <!-- TODO 芋艿:引入依赖要优化下: --> |
| 67 | <dependency> | 63 | <dependency> |
| 68 | <groupId>cn.iocoder.boot</groupId> | 64 | <groupId>cn.iocoder.boot</groupId> |
| 69 | - <artifactId>yudao-spring-boot-starter-mybatis</artifactId> | 65 | + <artifactId>yudao-spring-boot-starter-biz-pay</artifactId> |
| 70 | </dependency> | 66 | </dependency> |
| 71 | 67 | ||
| 72 | <!-- DB 相关 --> | 68 | <!-- DB 相关 --> |
| @@ -80,6 +76,12 @@ | @@ -80,6 +76,12 @@ | ||
| 80 | <groupId>cn.iocoder.boot</groupId> | 76 | <groupId>cn.iocoder.boot</groupId> |
| 81 | <artifactId>yudao-spring-boot-starter-test</artifactId> | 77 | <artifactId>yudao-spring-boot-starter-test</artifactId> |
| 82 | </dependency> | 78 | </dependency> |
| 79 | + | ||
| 80 | + <!-- 工具类相关 --> | ||
| 81 | + <dependency> | ||
| 82 | + <groupId>cn.iocoder.boot</groupId> | ||
| 83 | + <artifactId>yudao-spring-boot-starter-excel</artifactId> | ||
| 84 | + </dependency> | ||
| 83 | </dependencies> | 85 | </dependencies> |
| 84 | 86 | ||
| 85 | </project> | 87 | </project> |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java
| @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.validation.Mobile; | @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.validation.Mobile; | ||
| 4 | import lombok.Data; | 4 | import lombok.Data; |
| 5 | 5 | ||
| 6 | import javax.validation.constraints.NotEmpty; | 6 | import javax.validation.constraints.NotEmpty; |
| 7 | -import javax.validation.constraints.NotNull; | ||
| 8 | import java.util.Map; | 7 | import java.util.Map; |
| 9 | 8 | ||
| 10 | /** | 9 | /** |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java
| @@ -106,7 +106,7 @@ public class OAuth2OpenController { | @@ -106,7 +106,7 @@ public class OAuth2OpenController { | ||
| 106 | @RequestParam(value = "scope", required = false) String scope, // 密码模式 | 106 | @RequestParam(value = "scope", required = false) String scope, // 密码模式 |
| 107 | @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式 | 107 | @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式 |
| 108 | List<String> scopes = OAuth2Utils.buildScopes(scope); | 108 | List<String> scopes = OAuth2Utils.buildScopes(scope); |
| 109 | - // 授权类型 | 109 | + // 1.1 校验授权类型 |
| 110 | OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType); | 110 | OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType); |
| 111 | if (grantTypeEnum == null) { | 111 | if (grantTypeEnum == null) { |
| 112 | throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType)); | 112 | throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType)); |
| @@ -115,12 +115,12 @@ public class OAuth2OpenController { | @@ -115,12 +115,12 @@ public class OAuth2OpenController { | ||
| 115 | throw exception0(BAD_REQUEST.getCode(), "Token 接口不支持 implicit 授权模式"); | 115 | throw exception0(BAD_REQUEST.getCode(), "Token 接口不支持 implicit 授权模式"); |
| 116 | } | 116 | } |
| 117 | 117 | ||
| 118 | - // 校验客户端 | 118 | + // 1.2 校验客户端 |
| 119 | String[] clientIdAndSecret = obtainBasicAuthorization(request); | 119 | String[] clientIdAndSecret = obtainBasicAuthorization(request); |
| 120 | OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1], | 120 | OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1], |
| 121 | grantType, scopes, redirectUri); | 121 | grantType, scopes, redirectUri); |
| 122 | 122 | ||
| 123 | - // 根据授权模式,获取访问令牌 | 123 | + // 2. 根据授权模式,获取访问令牌 |
| 124 | OAuth2AccessTokenDO accessTokenDO; | 124 | OAuth2AccessTokenDO accessTokenDO; |
| 125 | switch (grantTypeEnum) { | 125 | switch (grantTypeEnum) { |
| 126 | case AUTHORIZATION_CODE: | 126 | case AUTHORIZATION_CODE: |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java
| @@ -26,6 +26,7 @@ public class OAuth2OpenCheckTokenRespVO { | @@ -26,6 +26,7 @@ public class OAuth2OpenCheckTokenRespVO { | ||
| 26 | private Long tenantId; | 26 | private Long tenantId; |
| 27 | 27 | ||
| 28 | @ApiModelProperty(value = "客户端编号", required = true, example = "car") | 28 | @ApiModelProperty(value = "客户端编号", required = true, example = "car") |
| 29 | + @JsonProperty("client_id") | ||
| 29 | private String clientId; | 30 | private String clientId; |
| 30 | @ApiModelProperty(value = "授权范围", required = true, example = "user_info") | 31 | @ApiModelProperty(value = "授权范围", required = true, example = "user_info") |
| 31 | private List<String> scopes; | 32 | private List<String> scopes; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java
| @@ -17,7 +17,7 @@ public class OAuth2UserInfoRespVO { | @@ -17,7 +17,7 @@ public class OAuth2UserInfoRespVO { | ||
| 17 | @ApiModelProperty(value = "用户编号", required = true, example = "1") | 17 | @ApiModelProperty(value = "用户编号", required = true, example = "1") |
| 18 | private Long id; | 18 | private Long id; |
| 19 | 19 | ||
| 20 | - @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿") | 20 | + @ApiModelProperty(value = "用户账号", required = true, example = "芋艿") |
| 21 | private String username; | 21 | private String username; |
| 22 | 22 | ||
| 23 | @ApiModelProperty(value = "用户昵称", required = true, example = "芋道") | 23 | @ApiModelProperty(value = "用户昵称", required = true, example = "芋道") |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java
| @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.*; | @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.*; | ||
| 6 | import cn.iocoder.yudao.module.system.convert.permission.MenuConvert; | 6 | import cn.iocoder.yudao.module.system.convert.permission.MenuConvert; |
| 7 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; | 7 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; |
| 8 | import cn.iocoder.yudao.module.system.service.permission.MenuService; | 8 | import cn.iocoder.yudao.module.system.service.permission.MenuService; |
| 9 | -import cn.iocoder.yudao.module.system.service.tenant.TenantService; | ||
| 10 | import io.swagger.annotations.Api; | 9 | import io.swagger.annotations.Api; |
| 11 | import io.swagger.annotations.ApiImplicitParam; | 10 | import io.swagger.annotations.ApiImplicitParam; |
| 12 | import io.swagger.annotations.ApiOperation; | 11 | import io.swagger.annotations.ApiOperation; |
| @@ -29,8 +28,6 @@ public class MenuController { | @@ -29,8 +28,6 @@ public class MenuController { | ||
| 29 | 28 | ||
| 30 | @Resource | 29 | @Resource |
| 31 | private MenuService menuService; | 30 | private MenuService menuService; |
| 32 | - @Resource | ||
| 33 | - private TenantService tenantService; | ||
| 34 | 31 | ||
| 35 | @PostMapping("/create") | 32 | @PostMapping("/create") |
| 36 | @ApiOperation("创建菜单") | 33 | @ApiOperation("创建菜单") |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantPackageDO.java
| @@ -34,7 +34,7 @@ public class TenantPackageDO extends BaseDO { | @@ -34,7 +34,7 @@ public class TenantPackageDO extends BaseDO { | ||
| 34 | */ | 34 | */ |
| 35 | private String name; | 35 | private String name; |
| 36 | /** | 36 | /** |
| 37 | - * 租户状态 | 37 | + * 租户套餐状态 |
| 38 | * | 38 | * |
| 39 | * 枚举 {@link CommonStatusEnum} | 39 | * 枚举 {@link CommonStatusEnum} |
| 40 | */ | 40 | */ |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantService.java
| @@ -12,7 +12,7 @@ import java.util.List; | @@ -12,7 +12,7 @@ import java.util.List; | ||
| 12 | * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。 | 12 | * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。 |
| 13 | * | 13 | * |
| 14 | * 问题:为什么自身也作为一个第三方应用,也走这套流程呢? | 14 | * 问题:为什么自身也作为一个第三方应用,也走这套流程呢? |
| 15 | - * 回复:当然可以这么做,采用 Implicit 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。 | 15 | + * 回复:当然可以这么做,采用 password 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。 |
| 16 | * | 16 | * |
| 17 | * @author 芋道源码 | 17 | * @author 芋道源码 |
| 18 | */ | 18 | */ |
yudao-server/src/main/resources/application-local.yaml
| @@ -45,33 +45,33 @@ spring: | @@ -45,33 +45,33 @@ spring: | ||
| 45 | datasource: | 45 | datasource: |
| 46 | master: | 46 | master: |
| 47 | name: ruoyi-vue-pro | 47 | name: ruoyi-vue-pro |
| 48 | - url: jdbc:mysql://139.9.196.247:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 | 48 | + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 |
| 49 | # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 | 49 | # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 |
| 50 | # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 | 50 | # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 |
| 51 | # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 | 51 | # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 |
| 52 | # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 | 52 | # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 |
| 53 | username: root | 53 | username: root |
| 54 | - password: ${RUOYI_VUE_PRO} | 54 | + password: 123456 |
| 55 | # username: sa | 55 | # username: sa |
| 56 | # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W | 56 | # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W |
| 57 | slave: # 模拟从库,可根据自己需要修改 | 57 | slave: # 模拟从库,可根据自己需要修改 |
| 58 | name: ruoyi-vue-pro | 58 | name: ruoyi-vue-pro |
| 59 | - url: jdbc:mysql://139.9.196.247:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 | 59 | + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 |
| 60 | # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 | 60 | # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 |
| 61 | # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 | 61 | # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 |
| 62 | # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 | 62 | # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 |
| 63 | # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 | 63 | # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 |
| 64 | username: root | 64 | username: root |
| 65 | - password: ${RUOYI_VUE_PRO} | 65 | + password: 123456 |
| 66 | # username: sa | 66 | # username: sa |
| 67 | # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W | 67 | # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W |
| 68 | 68 | ||
| 69 | # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 | 69 | # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 |
| 70 | redis: | 70 | redis: |
| 71 | - host: 139.9.196.247 # 地址 | 71 | + host: 127.0.0.1 # 地址 |
| 72 | port: 6379 # 端口 | 72 | port: 6379 # 端口 |
| 73 | database: 0 # 数据库索引 | 73 | database: 0 # 数据库索引 |
| 74 | - password: 123456 # 密码,建议生产环境开启 | 74 | +# password: 123456 # 密码,建议生产环境开启 |
| 75 | 75 | ||
| 76 | jasypt: | 76 | jasypt: |
| 77 | encryptor: | 77 | encryptor: |
| @@ -198,7 +198,7 @@ wx: | @@ -198,7 +198,7 @@ wx: | ||
| 198 | # 芋道配置项,设置当前项目所有自定义的配置 | 198 | # 芋道配置项,设置当前项目所有自定义的配置 |
| 199 | yudao: | 199 | yudao: |
| 200 | captcha: | 200 | captcha: |
| 201 | - enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试 | 201 | + enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试; |
| 202 | security: | 202 | security: |
| 203 | mock-enable: true | 203 | mock-enable: true |
| 204 | xss: | 204 | xss: |
yudao-ui-admin-vue3/README.md
| @@ -27,17 +27,17 @@ | @@ -27,17 +27,17 @@ | ||
| 27 | 27 | ||
| 28 | | 框架 | 说明 | 版本 | | 28 | | 框架 | 说明 | 版本 | |
| 29 | | --- | --- |--------| | 29 | | --- | --- |--------| |
| 30 | -| [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.37 | | ||
| 31 | -| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.4 | | ||
| 32 | -| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.12 | | ||
| 33 | -| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.7.4 | | ||
| 34 | -| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.17 | | ||
| 35 | -| [vueuse](https://vueuse.org//) | 常用工具集 | 9.0.2 | | ||
| 36 | -| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.0 | | ||
| 37 | -| [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.3 | | 30 | +| [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.41 | |
| 31 | +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.1.8 | | ||
| 32 | +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.18 | | ||
| 33 | +| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.8.4 | | ||
| 34 | +| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.23 | | ||
| 35 | +| [vueuse](https://vueuse.org//) | 常用工具集 | 9.3.1 | | ||
| 36 | +| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 | | ||
| 37 | +| [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.5 | | ||
| 38 | | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | | 38 | | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | |
| 39 | -| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | | ||
| 40 | -| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.14 | | 39 | +| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.0.0 | |
| 40 | +| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.21 | | ||
| 41 | 41 | ||
| 42 | ### 推荐 VScode 开发,插件如下 | 42 | ### 推荐 VScode 开发,插件如下 |
| 43 | 43 |
yudao-ui-admin-vue3/package.json
| 1 | { | 1 | { |
| 2 | "name": "ruoyi-vue-pro-vue3", | 2 | "name": "ruoyi-vue-pro-vue3", |
| 3 | - "version": "1.6.3.1611", | 3 | + "version": "1.6.4.1811", |
| 4 | "description": "基于vue3、element-plus、typesScript、vite3", | 4 | "description": "基于vue3、element-plus、typesScript、vite3", |
| 5 | "author": "xingyu", | 5 | "author": "xingyu", |
| 6 | "private": false, | 6 | "private": false, |
| @@ -10,7 +10,7 @@ | @@ -10,7 +10,7 @@ | ||
| 10 | "ts:check": "vue-tsc --noEmit", | 10 | "ts:check": "vue-tsc --noEmit", |
| 11 | "build:pro": "vite build --mode pro", | 11 | "build:pro": "vite build --mode pro", |
| 12 | "build:dev": "vite build --mode dev", | 12 | "build:dev": "vite build --mode dev", |
| 13 | - "build:test": "pnpm run ts:check && vite build --mode test", | 13 | + "build:test": "npm run ts:check && vite build --mode test", |
| 14 | "serve:pro": "vite preview --mode pro", | 14 | "serve:pro": "vite preview --mode pro", |
| 15 | "serve:dev": "vite preview --mode dev", | 15 | "serve:dev": "vite preview --mode dev", |
| 16 | "serve:test": "vite preview --mode test", | 16 | "serve:test": "vite preview --mode test", |
| @@ -25,84 +25,84 @@ | @@ -25,84 +25,84 @@ | ||
| 25 | "p": "plop" | 25 | "p": "plop" |
| 26 | }, | 26 | }, |
| 27 | "dependencies": { | 27 | "dependencies": { |
| 28 | - "@iconify/iconify": "^2.2.1", | ||
| 29 | - "@vueuse/core": "^9.1.0", | ||
| 30 | - "@wangeditor/editor": "^5.1.14", | 28 | + "@iconify/iconify": "^3.0.0", |
| 29 | + "@vueuse/core": "^9.3.1", | ||
| 30 | + "@wangeditor/editor": "^5.1.21", | ||
| 31 | "@wangeditor/editor-for-vue": "^5.1.10", | 31 | "@wangeditor/editor-for-vue": "^5.1.10", |
| 32 | - "@zxcvbn-ts/core": "^2.0.4", | 32 | + "@zxcvbn-ts/core": "^2.0.5", |
| 33 | "animate.css": "^4.1.1", | 33 | "animate.css": "^4.1.1", |
| 34 | "axios": "^0.27.2", | 34 | "axios": "^0.27.2", |
| 35 | "crypto-js": "^4.1.1", | 35 | "crypto-js": "^4.1.1", |
| 36 | - "dayjs": "^1.11.4", | ||
| 37 | - "echarts": "^5.3.3", | 36 | + "dayjs": "^1.11.5", |
| 37 | + "echarts": "^5.4.0", | ||
| 38 | "echarts-wordcloud": "^2.0.0", | 38 | "echarts-wordcloud": "^2.0.0", |
| 39 | - "element-plus": "2.2.12", | 39 | + "element-plus": "2.2.18", |
| 40 | "intro.js": "^6.0.0", | 40 | "intro.js": "^6.0.0", |
| 41 | "jsencrypt": "^3.2.1", | 41 | "jsencrypt": "^3.2.1", |
| 42 | "lodash-es": "^4.17.21", | 42 | "lodash-es": "^4.17.21", |
| 43 | "mitt": "^3.0.0", | 43 | "mitt": "^3.0.0", |
| 44 | "nprogress": "^0.2.0", | 44 | "nprogress": "^0.2.0", |
| 45 | - "pinia": "^2.0.17", | ||
| 46 | - "pinia-plugin-persist": "^1.0.0", | 45 | + "pinia": "^2.0.23", |
| 47 | "qrcode": "^1.5.1", | 46 | "qrcode": "^1.5.1", |
| 48 | "qs": "^6.11.0", | 47 | "qs": "^6.11.0", |
| 49 | "url": "^0.11.0", | 48 | "url": "^0.11.0", |
| 50 | - "vue": "3.2.37", | 49 | + "vue": "3.2.41", |
| 51 | "vue-cropper": "^1.0.3", | 50 | "vue-cropper": "^1.0.3", |
| 52 | "vue-i18n": "9.2.2", | 51 | "vue-i18n": "9.2.2", |
| 53 | - "vue-router": "^4.1.3", | 52 | + "vue-router": "^4.1.5", |
| 54 | "vue-types": "^4.2.1", | 53 | "vue-types": "^4.2.1", |
| 55 | "web-storage-cache": "^1.1.1" | 54 | "web-storage-cache": "^1.1.1" |
| 56 | }, | 55 | }, |
| 57 | "devDependencies": { | 56 | "devDependencies": { |
| 58 | - "@commitlint/cli": "^17.0.3", | ||
| 59 | - "@commitlint/config-conventional": "^17.0.3", | ||
| 60 | - "@iconify/json": "^2.1.89", | ||
| 61 | - "@intlify/vite-plugin-vue-i18n": "^6.0.0", | 57 | + "@commitlint/cli": "^17.1.2", |
| 58 | + "@commitlint/config-conventional": "^17.1.0", | ||
| 59 | + "@iconify/json": "^2.1.122", | ||
| 60 | + "@intlify/vite-plugin-vue-i18n": "^6.0.3", | ||
| 62 | "@purge-icons/generated": "^0.9.0", | 61 | "@purge-icons/generated": "^0.9.0", |
| 63 | "@types/intro.js": "^5.1.0", | 62 | "@types/intro.js": "^5.1.0", |
| 64 | "@types/lodash-es": "^4.17.6", | 63 | "@types/lodash-es": "^4.17.6", |
| 65 | - "@types/node": "^18.6.5", | 64 | + "@types/node": "^18.11.0", |
| 66 | "@types/nprogress": "^0.2.0", | 65 | "@types/nprogress": "^0.2.0", |
| 67 | - "@types/qrcode": "^1.4.2", | 66 | + "@types/qrcode": "^1.5.0", |
| 68 | "@types/qs": "^6.9.7", | 67 | "@types/qs": "^6.9.7", |
| 69 | - "@typescript-eslint/eslint-plugin": "^5.33.0", | ||
| 70 | - "@typescript-eslint/parser": "^5.33.0", | ||
| 71 | - "@vitejs/plugin-vue": "^3.0.1", | ||
| 72 | - "@vitejs/plugin-vue-jsx": "^2.0.0", | ||
| 73 | - "autoprefixer": "^10.4.8", | ||
| 74 | - "eslint": "^8.21.0", | 68 | + "@typescript-eslint/eslint-plugin": "^5.40.0", |
| 69 | + "@typescript-eslint/parser": "^5.40.0", | ||
| 70 | + "@vitejs/plugin-vue": "^3.1.2", | ||
| 71 | + "@vitejs/plugin-vue-jsx": "^2.0.1", | ||
| 72 | + "autoprefixer": "^10.4.12", | ||
| 73 | + "eslint": "^8.25.0", | ||
| 75 | "eslint-config-prettier": "^8.5.0", | 74 | "eslint-config-prettier": "^8.5.0", |
| 76 | - "eslint-define-config": "^1.6.0", | 75 | + "eslint-define-config": "^1.7.0", |
| 77 | "eslint-plugin-prettier": "^4.2.1", | 76 | "eslint-plugin-prettier": "^4.2.1", |
| 78 | - "eslint-plugin-vue": "^9.3.0", | 77 | + "eslint-plugin-vue": "^9.6.0", |
| 79 | "less": "^4.1.3", | 78 | "less": "^4.1.3", |
| 80 | "lint-staged": "^13.0.3", | 79 | "lint-staged": "^13.0.3", |
| 81 | "plop": "^3.1.1", | 80 | "plop": "^3.1.1", |
| 82 | - "postcss": "^8.4.16", | 81 | + "postcss": "^8.4.18", |
| 83 | "postcss-html": "^1.5.0", | 82 | "postcss-html": "^1.5.0", |
| 84 | "postcss-less": "^6.0.0", | 83 | "postcss-less": "^6.0.0", |
| 85 | "prettier": "^2.7.1", | 84 | "prettier": "^2.7.1", |
| 86 | "rimraf": "^3.0.2", | 85 | "rimraf": "^3.0.2", |
| 87 | - "rollup": "^2.77.2", | ||
| 88 | - "stylelint": "^14.9.1", | 86 | + "rollup": "^2.79.1", |
| 87 | + "stylelint": "^14.14.0", | ||
| 89 | "stylelint-config-html": "^1.1.0", | 88 | "stylelint-config-html": "^1.1.0", |
| 90 | "stylelint-config-prettier": "^9.0.3", | 89 | "stylelint-config-prettier": "^9.0.3", |
| 91 | - "stylelint-config-recommended": "^8.0.0", | ||
| 92 | - "stylelint-config-standard": "^26.0.0", | 90 | + "stylelint-config-recommended": "^9.0.0", |
| 91 | + "stylelint-config-standard": "^29.0.0", | ||
| 93 | "stylelint-order": "^5.0.0", | 92 | "stylelint-order": "^5.0.0", |
| 94 | - "typescript": "4.7.4", | ||
| 95 | - "unplugin-vue-define-options": "^0.7.3", | ||
| 96 | - "vite": "3.0.5", | 93 | + "typescript": "4.8.4", |
| 94 | + "unplugin-vue-macros": "^0.13.2", | ||
| 95 | + "vite": "3.1.8", | ||
| 97 | "vite-plugin-compression": "^0.5.1", | 96 | "vite-plugin-compression": "^0.5.1", |
| 98 | - "vite-plugin-eslint": "^1.7.0", | 97 | + "vite-plugin-eslint": "^1.8.1", |
| 99 | "vite-plugin-html": "^3.2.0", | 98 | "vite-plugin-html": "^3.2.0", |
| 100 | - "vite-plugin-purge-icons": "^0.9.0", | ||
| 101 | - "vite-plugin-style-import": "^2.0.0", | 99 | + "vite-plugin-purge-icons": "^0.9.1", |
| 100 | + "vite-plugin-style-import": "2.0.0", | ||
| 102 | "vite-plugin-svg-icons": "^2.0.1", | 101 | "vite-plugin-svg-icons": "^2.0.1", |
| 103 | - "vite-plugin-windicss": "^1.8.7", | ||
| 104 | - "vue-tsc": "^0.39.5", | ||
| 105 | - "windicss": "^3.5.6" | 102 | + "vite-plugin-windicss": "^1.8.8", |
| 103 | + "vue-tsc": "^1.0.8", | ||
| 104 | + "windicss": "^3.5.6", | ||
| 105 | + "windicss-analysis": "^0.3.5" | ||
| 106 | }, | 106 | }, |
| 107 | "engines": { | 107 | "engines": { |
| 108 | "node": ">= 14.18.0" | 108 | "node": ">= 14.18.0" |
yudao-ui-admin-vue3/src/api/system/tenant/types.ts
| @@ -4,6 +4,8 @@ export type TenantVO = { | @@ -4,6 +4,8 @@ export type TenantVO = { | ||
| 4 | packageId: number | 4 | packageId: number |
| 5 | contactName: string | 5 | contactName: string |
| 6 | contactMobile: string | 6 | contactMobile: string |
| 7 | + username: string | ||
| 8 | + password: string | ||
| 7 | accountCount: number | 9 | accountCount: number |
| 8 | expireTime: string | 10 | expireTime: string |
| 9 | domain: string | 11 | domain: string |
yudao-ui-admin-vue3/src/components/ConfigGlobal/src/ConfigGlobal.vue
| @@ -7,13 +7,14 @@ import { useWindowSize } from '@vueuse/core' | @@ -7,13 +7,14 @@ import { useWindowSize } from '@vueuse/core' | ||
| 7 | import { useAppStore } from '@/store/modules/app' | 7 | import { useAppStore } from '@/store/modules/app' |
| 8 | import { setCssVar } from '@/utils' | 8 | import { setCssVar } from '@/utils' |
| 9 | import { useDesign } from '@/hooks/web/useDesign' | 9 | import { useDesign } from '@/hooks/web/useDesign' |
| 10 | +import { ElementPlusSize } from '@/types/elementPlus' | ||
| 10 | 11 | ||
| 11 | const { variables } = useDesign() | 12 | const { variables } = useDesign() |
| 12 | 13 | ||
| 13 | const appStore = useAppStore() | 14 | const appStore = useAppStore() |
| 14 | 15 | ||
| 15 | const props = defineProps({ | 16 | const props = defineProps({ |
| 16 | - size: propTypes.oneOf<ElememtPlusSize[]>(['default', 'small', 'large']).def('default') | 17 | + size: propTypes.oneOf<ElementPlusSize[]>(['default', 'small', 'large']).def('default') |
| 17 | }) | 18 | }) |
| 18 | 19 | ||
| 19 | provide('configGlobal', props) | 20 | provide('configGlobal', props) |
yudao-ui-admin-vue3/src/components/ContextMenu/src/ContextMenu.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus' | 2 | import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus' |
| 3 | -import { PropType, ref } from 'vue' | 3 | +import { PropType, ref, defineEmits } from 'vue' |
| 4 | import { useI18n } from '@/hooks/web/useI18n' | 4 | import { useI18n } from '@/hooks/web/useI18n' |
| 5 | import { useDesign } from '@/hooks/web/useDesign' | 5 | import { useDesign } from '@/hooks/web/useDesign' |
| 6 | import type { RouteLocationNormalizedLoaded } from 'vue-router' | 6 | import type { RouteLocationNormalizedLoaded } from 'vue-router' |
| 7 | +import { contextMenuSchema } from '../../../types/contextMenu' | ||
| 7 | 8 | ||
| 8 | const { getPrefixCls } = useDesign() | 9 | const { getPrefixCls } = useDesign() |
| 9 | 10 |
yudao-ui-admin-vue3/src/components/CountTo/src/CountTo.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | -import { reactive, computed, watch, onMounted, unref, toRef, PropType } from 'vue' | 2 | +import { reactive, computed, watch, onMounted, unref, toRef, PropType, defineEmits } from 'vue' |
| 3 | import { isNumber } from '@/utils/is' | 3 | import { isNumber } from '@/utils/is' |
| 4 | import { propTypes } from '@/utils/propTypes' | 4 | import { propTypes } from '@/utils/propTypes' |
| 5 | import { useDesign } from '@/hooks/web/useDesign' | 5 | import { useDesign } from '@/hooks/web/useDesign' |
yudao-ui-admin-vue3/src/components/Crontab/src/Crontab.vue
| @@ -24,7 +24,7 @@ import { | @@ -24,7 +24,7 @@ import { | ||
| 24 | ElRadioButton, | 24 | ElRadioButton, |
| 25 | ElMessage | 25 | ElMessage |
| 26 | } from 'element-plus' | 26 | } from 'element-plus' |
| 27 | -import { ref, computed, onMounted, reactive, watch, PropType } from 'vue' | 27 | +import { ref, computed, onMounted, reactive, watch, PropType, defineEmits } from 'vue' |
| 28 | interface shortcutsType { | 28 | interface shortcutsType { |
| 29 | text: string | 29 | text: string |
| 30 | value: string | 30 | value: string |
yudao-ui-admin-vue3/src/components/Descriptions/src/Descriptions.vue
| @@ -4,6 +4,7 @@ import { useDesign } from '@/hooks/web/useDesign' | @@ -4,6 +4,7 @@ import { useDesign } from '@/hooks/web/useDesign' | ||
| 4 | import { propTypes } from '@/utils/propTypes' | 4 | import { propTypes } from '@/utils/propTypes' |
| 5 | import { ref, unref, PropType, computed, useAttrs } from 'vue' | 5 | import { ref, unref, PropType, computed, useAttrs } from 'vue' |
| 6 | import { useAppStore } from '@/store/modules/app' | 6 | import { useAppStore } from '@/store/modules/app' |
| 7 | +import { DescriptionsSchema } from '@/types/descriptions' | ||
| 7 | 8 | ||
| 8 | const appStore = useAppStore() | 9 | const appStore = useAppStore() |
| 9 | 10 |
yudao-ui-admin-vue3/src/components/Dialog/src/Dialog.vue
| @@ -74,7 +74,7 @@ const dialogStyle = computed(() => { | @@ -74,7 +74,7 @@ const dialogStyle = computed(() => { | ||
| 74 | </slot> | 74 | </slot> |
| 75 | <Icon | 75 | <Icon |
| 76 | v-if="fullscreen" | 76 | v-if="fullscreen" |
| 77 | - class="mr-22px cursor-pointer is-hover mt-2px" | 77 | + class="mr-22px cursor-pointer is-hover mt-2px z-10" |
| 78 | :icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'" | 78 | :icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'" |
| 79 | color="var(--el-color-info)" | 79 | color="var(--el-color-info)" |
| 80 | @click="toggleFull" | 80 | @click="toggleFull" |
yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue
| @@ -177,7 +177,7 @@ defineExpose({ | @@ -177,7 +177,7 @@ defineExpose({ | ||
| 177 | </script> | 177 | </script> |
| 178 | 178 | ||
| 179 | <template> | 179 | <template> |
| 180 | - <div class="border-1 border-solid border-[var(--tags-view-border-color)]"> | 180 | + <div class="border-1 border-solid border-[var(--tags-view-border-color)] z-3000"> |
| 181 | <!-- 工具栏 --> | 181 | <!-- 工具栏 --> |
| 182 | <Toolbar | 182 | <Toolbar |
| 183 | :editor="editorRef" | 183 | :editor="editorRef" |
yudao-ui-admin-vue3/src/components/Error/src/Error.vue
| @@ -4,6 +4,7 @@ import networkError from '@/assets/svgs/500.svg' | @@ -4,6 +4,7 @@ import networkError from '@/assets/svgs/500.svg' | ||
| 4 | import noPermission from '@/assets/svgs/403.svg' | 4 | import noPermission from '@/assets/svgs/403.svg' |
| 5 | import { propTypes } from '@/utils/propTypes' | 5 | import { propTypes } from '@/utils/propTypes' |
| 6 | import { useI18n } from '@/hooks/web/useI18n' | 6 | import { useI18n } from '@/hooks/web/useI18n' |
| 7 | +import { defineEmits } from 'vue' | ||
| 7 | 8 | ||
| 8 | interface ErrorMap { | 9 | interface ErrorMap { |
| 9 | url: string | 10 | url: string |
yudao-ui-admin-vue3/src/components/Form/index.ts
| 1 | import Form from './src/Form.vue' | 1 | import Form from './src/Form.vue' |
| 2 | import { ElForm } from 'element-plus' | 2 | import { ElForm } from 'element-plus' |
| 3 | +import { FormSchema, FormSetPropsType } from '@/types/form' | ||
| 3 | 4 | ||
| 4 | export interface FormExpose { | 5 | export interface FormExpose { |
| 5 | setValues: (data: Recordable) => void | 6 | setValues: (data: Recordable) => void |
yudao-ui-admin-vue3/src/components/Form/src/Form.vue
| @@ -20,6 +20,7 @@ import { findIndex } from '@/utils' | @@ -20,6 +20,7 @@ import { findIndex } from '@/utils' | ||
| 20 | import { set } from 'lodash-es' | 20 | import { set } from 'lodash-es' |
| 21 | import { FormProps } from './types' | 21 | import { FormProps } from './types' |
| 22 | import { Icon } from '@/components/Icon' | 22 | import { Icon } from '@/components/Icon' |
| 23 | +import { FormSchema, FormSetPropsType } from '@/types/form' | ||
| 23 | 24 | ||
| 24 | const { getPrefixCls } = useDesign() | 25 | const { getPrefixCls } = useDesign() |
| 25 | 26 |
yudao-ui-admin-vue3/src/components/Form/src/componentMap.ts
| @@ -20,6 +20,7 @@ import { | @@ -20,6 +20,7 @@ import { | ||
| 20 | } from 'element-plus' | 20 | } from 'element-plus' |
| 21 | import { InputPassword } from '@/components/InputPassword' | 21 | import { InputPassword } from '@/components/InputPassword' |
| 22 | import { Editor } from '@/components/Editor' | 22 | import { Editor } from '@/components/Editor' |
| 23 | +import { ComponentName } from '@/types/components' | ||
| 23 | 24 | ||
| 24 | const componentMap: Recordable<Component, ComponentName> = { | 25 | const componentMap: Recordable<Component, ComponentName> = { |
| 25 | Radio: ElRadioGroup, | 26 | Radio: ElRadioGroup, |
yudao-ui-admin-vue3/src/components/Form/src/components/useRenderCheckbox.tsx
yudao-ui-admin-vue3/src/components/Form/src/components/useRenderRadio.tsx
yudao-ui-admin-vue3/src/components/Form/src/components/useRenderSelect.tsx
| 1 | +import { FormSchema } from '@/types/form' | ||
| 2 | +import { ComponentOptions } from '@/types/components' | ||
| 1 | import { ElOption, ElOptionGroup } from 'element-plus' | 3 | import { ElOption, ElOptionGroup } from 'element-plus' |
| 2 | import { getSlot } from '@/utils/tsxHelper' | 4 | import { getSlot } from '@/utils/tsxHelper' |
| 3 | import { Slots } from 'vue' | 5 | import { Slots } from 'vue' |
| @@ -29,8 +31,15 @@ export const useRenderSelect = (slots: Slots) => { | @@ -29,8 +31,15 @@ export const useRenderSelect = (slots: Slots) => { | ||
| 29 | // 如果有别名,就取别名 | 31 | // 如果有别名,就取别名 |
| 30 | const labelAlias = item?.componentProps?.optionsAlias?.labelField | 32 | const labelAlias = item?.componentProps?.optionsAlias?.labelField |
| 31 | const valueAlias = item?.componentProps?.optionsAlias?.valueField | 33 | const valueAlias = item?.componentProps?.optionsAlias?.valueField |
| 34 | + | ||
| 35 | + const { label, value, ...other } = option | ||
| 36 | + | ||
| 32 | return ( | 37 | return ( |
| 33 | - <ElOption label={option[labelAlias || 'label']} value={option[valueAlias || 'value']}> | 38 | + <ElOption |
| 39 | + label={labelAlias ? option[labelAlias] : label} | ||
| 40 | + value={valueAlias ? option[valueAlias] : value} | ||
| 41 | + {...other} | ||
| 42 | + > | ||
| 34 | {{ | 43 | {{ |
| 35 | default: () => | 44 | default: () => |
| 36 | // option 插槽名规则,{field}-option | 45 | // option 插槽名规则,{field}-option |
yudao-ui-admin-vue3/src/components/Form/src/helper.ts
| @@ -2,6 +2,8 @@ import { useI18n } from '@/hooks/web/useI18n' | @@ -2,6 +2,8 @@ import { useI18n } from '@/hooks/web/useI18n' | ||
| 2 | import type { Slots } from 'vue' | 2 | import type { Slots } from 'vue' |
| 3 | import { getSlot } from '@/utils/tsxHelper' | 3 | import { getSlot } from '@/utils/tsxHelper' |
| 4 | import { PlaceholderMoel } from './types' | 4 | import { PlaceholderMoel } from './types' |
| 5 | +import { FormSchema } from '@/types/form' | ||
| 6 | +import { ColProps } from '@/types/components' | ||
| 5 | 7 | ||
| 6 | /** | 8 | /** |
| 7 | * | 9 | * |
yudao-ui-admin-vue3/src/components/Form/src/types.ts
yudao-ui-admin-vue3/src/components/Icon/src/IconSelect.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | import { cloneDeep } from 'lodash-es' | 2 | import { cloneDeep } from 'lodash-es' |
| 3 | -import { ref, computed, CSSProperties, toRef, watch } from 'vue' | 3 | +import { ref, computed, CSSProperties, toRef, watch, defineEmits } from 'vue' |
| 4 | import { | 4 | import { |
| 5 | ElInput, | 5 | ElInput, |
| 6 | ElPopover, | 6 | ElPopover, |
yudao-ui-admin-vue3/src/components/Infotip/src/Infotip.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | -import { PropType } from 'vue' | ||
| 3 | -import { Highlight } from '@//components/Highlight' | 2 | +import { PropType, defineEmits } from 'vue' |
| 3 | +import { Highlight } from '@/components/Highlight' | ||
| 4 | import { useDesign } from '@/hooks/web/useDesign' | 4 | import { useDesign } from '@/hooks/web/useDesign' |
| 5 | import { propTypes } from '@/utils/propTypes' | 5 | import { propTypes } from '@/utils/propTypes' |
| 6 | +import { TipSchema } from '@/types/infoTip' | ||
| 6 | 7 | ||
| 7 | const { getPrefixCls } = useDesign() | 8 | const { getPrefixCls } = useDesign() |
| 8 | 9 |
yudao-ui-admin-vue3/src/components/InputPassword/src/InputPassword.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | -import { ref, unref, computed, watch } from 'vue' | 2 | +import { ref, unref, computed, watch, defineEmits } from 'vue' |
| 3 | import { ElInput } from 'element-plus' | 3 | import { ElInput } from 'element-plus' |
| 4 | import { propTypes } from '@/utils/propTypes' | 4 | import { propTypes } from '@/utils/propTypes' |
| 5 | import { useConfigGlobal } from '@/hooks/web/useConfigGlobal' | 5 | import { useConfigGlobal } from '@/hooks/web/useConfigGlobal' |
yudao-ui-admin-vue3/src/components/Menu/src/Menu.vue
| @@ -3,11 +3,11 @@ import { computed, defineComponent, unref, PropType } from 'vue' | @@ -3,11 +3,11 @@ import { computed, defineComponent, unref, PropType } from 'vue' | ||
| 3 | import { ElMenu, ElScrollbar } from 'element-plus' | 3 | import { ElMenu, ElScrollbar } from 'element-plus' |
| 4 | import { useAppStore } from '@/store/modules/app' | 4 | import { useAppStore } from '@/store/modules/app' |
| 5 | import { usePermissionStore } from '@/store/modules/permission' | 5 | import { usePermissionStore } from '@/store/modules/permission' |
| 6 | -import type { LayoutType } from '@/config/app' | ||
| 7 | import { useRenderMenuItem } from './components/useRenderMenuItem' | 6 | import { useRenderMenuItem } from './components/useRenderMenuItem' |
| 8 | import { useRouter } from 'vue-router' | 7 | import { useRouter } from 'vue-router' |
| 9 | import { isUrl } from '@/utils/is' | 8 | import { isUrl } from '@/utils/is' |
| 10 | import { useDesign } from '@/hooks/web/useDesign' | 9 | import { useDesign } from '@/hooks/web/useDesign' |
| 10 | +import { LayoutType } from '@/types/layout' | ||
| 11 | 11 | ||
| 12 | const { getPrefixCls } = useDesign() | 12 | const { getPrefixCls } = useDesign() |
| 13 | 13 |
yudao-ui-admin-vue3/src/components/Qrcode/src/Qrcode.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | -import { PropType, nextTick, ref, watch, computed, unref } from 'vue' | 2 | +import { PropType, nextTick, ref, watch, computed, unref, defineEmits } from 'vue' |
| 3 | import QRCode from 'qrcode' | 3 | import QRCode from 'qrcode' |
| 4 | import { QRCodeRenderersOptions } from 'qrcode' | 4 | import { QRCodeRenderersOptions } from 'qrcode' |
| 5 | import { cloneDeep } from 'lodash-es' | 5 | import { cloneDeep } from 'lodash-es' |
| 6 | import { propTypes } from '@/utils/propTypes' | 6 | import { propTypes } from '@/utils/propTypes' |
| 7 | import { useDesign } from '@/hooks/web/useDesign' | 7 | import { useDesign } from '@/hooks/web/useDesign' |
| 8 | import { isString } from '@/utils/is' | 8 | import { isString } from '@/utils/is' |
| 9 | +import { QrcodeLogo } from '@/types/qrcode' | ||
| 9 | 10 | ||
| 10 | const props = defineProps({ | 11 | const props = defineProps({ |
| 11 | // img 或者 canvas,img不支持logo嵌套 | 12 | // img 或者 canvas,img不支持logo嵌套 |
yudao-ui-admin-vue3/src/components/Search/src/Search.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | import { Form } from '@/components/Form' | 2 | import { Form } from '@/components/Form' |
| 3 | -import { PropType, computed, unref, ref } from 'vue' | 3 | +import { PropType, computed, unref, ref, defineEmits } from 'vue' |
| 4 | import { propTypes } from '@/utils/propTypes' | 4 | import { propTypes } from '@/utils/propTypes' |
| 5 | import { ElButton } from 'element-plus' | 5 | import { ElButton } from 'element-plus' |
| 6 | import { useI18n } from '@/hooks/web/useI18n' | 6 | import { useI18n } from '@/hooks/web/useI18n' |
| 7 | import { useForm } from '@/hooks/web/useForm' | 7 | import { useForm } from '@/hooks/web/useForm' |
| 8 | import { findIndex } from '@/utils' | 8 | import { findIndex } from '@/utils' |
| 9 | import { cloneDeep } from 'lodash-es' | 9 | import { cloneDeep } from 'lodash-es' |
| 10 | +import { FormSchema } from '@/types/form' | ||
| 10 | 11 | ||
| 11 | const { t } = useI18n() | 12 | const { t } = useI18n() |
| 12 | 13 |
yudao-ui-admin-vue3/src/components/Setting/src/Setting.vue
| @@ -208,7 +208,7 @@ const clear = () => { | @@ -208,7 +208,7 @@ const clear = () => { | ||
| 208 | <Icon icon="ep:setting" color="#fff" /> | 208 | <Icon icon="ep:setting" color="#fff" /> |
| 209 | </div> | 209 | </div> |
| 210 | 210 | ||
| 211 | - <ElDrawer v-model="drawer" direction="rtl" size="350px"> | 211 | + <ElDrawer v-model="drawer" direction="rtl" size="350px" :z-index="4000"> |
| 212 | <template #header> | 212 | <template #header> |
| 213 | <span class="text-16px font-700">{{ t('setting.projectSetting') }}</span> | 213 | <span class="text-16px font-700">{{ t('setting.projectSetting') }}</span> |
| 214 | </template> | 214 | </template> |
yudao-ui-admin-vue3/src/components/Setting/src/components/ColorRadioPicker.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | -import { PropType, watch, unref, ref } from 'vue' | 2 | +import { PropType, watch, unref, ref, defineEmits } from 'vue' |
| 3 | import { propTypes } from '@/utils/propTypes' | 3 | import { propTypes } from '@/utils/propTypes' |
| 4 | import { useDesign } from '@/hooks/web/useDesign' | 4 | import { useDesign } from '@/hooks/web/useDesign' |
| 5 | 5 |
yudao-ui-admin-vue3/src/components/Setting/src/components/InterfaceDisplay.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | -import { ElSwitch } from 'element-plus' | 2 | +import { ElSwitch, ElMessage } from 'element-plus' |
| 3 | import { useI18n } from '@/hooks/web/useI18n' | 3 | import { useI18n } from '@/hooks/web/useI18n' |
| 4 | import { useAppStore } from '@/store/modules/app' | 4 | import { useAppStore } from '@/store/modules/app' |
| 5 | import { computed, ref, watch } from 'vue' | 5 | import { computed, ref, watch } from 'vue' |
| @@ -107,6 +107,21 @@ const greyModeChange = (show: boolean) => { | @@ -107,6 +107,21 @@ const greyModeChange = (show: boolean) => { | ||
| 107 | appStore.setGreyMode(show) | 107 | appStore.setGreyMode(show) |
| 108 | } | 108 | } |
| 109 | 109 | ||
| 110 | +// 动态路由 | ||
| 111 | +const dynamicRouter = ref(appStore.getDynamicRouter) | ||
| 112 | + | ||
| 113 | +const dynamicRouterChange = (show: boolean) => { | ||
| 114 | + ElMessage.info(t('setting.reExperienced')) | ||
| 115 | + appStore.setDynamicRouter(show) | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +// 固定菜单 | ||
| 119 | +const fixedMenu = ref(appStore.getFixedMenu) | ||
| 120 | + | ||
| 121 | +const fixedMenuChange = (show: boolean) => { | ||
| 122 | + appStore.setFixedMenu(show) | ||
| 123 | +} | ||
| 124 | + | ||
| 110 | const layout = computed(() => appStore.getLayout) | 125 | const layout = computed(() => appStore.getLayout) |
| 111 | 126 | ||
| 112 | watch( | 127 | watch( |
| @@ -185,5 +200,15 @@ watch( | @@ -185,5 +200,15 @@ watch( | ||
| 185 | <span class="text-14px">{{ t('setting.greyMode') }}</span> | 200 | <span class="text-14px">{{ t('setting.greyMode') }}</span> |
| 186 | <ElSwitch v-model="greyMode" @change="greyModeChange" /> | 201 | <ElSwitch v-model="greyMode" @change="greyModeChange" /> |
| 187 | </div> | 202 | </div> |
| 203 | + | ||
| 204 | + <div class="flex justify-between items-center"> | ||
| 205 | + <span class="text-14px">{{ t('setting.dynamicRouter') }}</span> | ||
| 206 | + <ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" /> | ||
| 207 | + </div> | ||
| 208 | + | ||
| 209 | + <div class="flex justify-between items-center"> | ||
| 210 | + <span class="text-14px">{{ t('setting.fixedMenu') }}</span> | ||
| 211 | + <ElSwitch v-model="fixedMenu" @change="fixedMenuChange" /> | ||
| 212 | + </div> | ||
| 188 | </div> | 213 | </div> |
| 189 | </template> | 214 | </template> |
yudao-ui-admin-vue3/src/components/SizeDropdown/src/SizeDropdown.vue
| @@ -5,6 +5,7 @@ import { useAppStore } from '@/store/modules/app' | @@ -5,6 +5,7 @@ import { useAppStore } from '@/store/modules/app' | ||
| 5 | import { useI18n } from '@/hooks/web/useI18n' | 5 | import { useI18n } from '@/hooks/web/useI18n' |
| 6 | import { propTypes } from '@/utils/propTypes' | 6 | import { propTypes } from '@/utils/propTypes' |
| 7 | import { useDesign } from '@/hooks/web/useDesign' | 7 | import { useDesign } from '@/hooks/web/useDesign' |
| 8 | +import { ElementPlusSize } from '@/types/elementPlus' | ||
| 8 | 9 | ||
| 9 | const { getPrefixCls } = useDesign() | 10 | const { getPrefixCls } = useDesign() |
| 10 | 11 | ||
| @@ -20,7 +21,7 @@ const appStore = useAppStore() | @@ -20,7 +21,7 @@ const appStore = useAppStore() | ||
| 20 | 21 | ||
| 21 | const sizeMap = computed(() => appStore.sizeMap) | 22 | const sizeMap = computed(() => appStore.sizeMap) |
| 22 | 23 | ||
| 23 | -const setCurrentSize = (size: ElememtPlusSize) => { | 24 | +const setCurrentSize = (size: ElementPlusSize) => { |
| 24 | appStore.setCurrentSize(size) | 25 | appStore.setCurrentSize(size) |
| 25 | } | 26 | } |
| 26 | </script> | 27 | </script> |
yudao-ui-admin-vue3/src/components/TabMenu/src/TabMenu.vue
| 1 | <script lang="tsx"> | 1 | <script lang="tsx"> |
| 2 | import { usePermissionStore } from '@/store/modules/permission' | 2 | import { usePermissionStore } from '@/store/modules/permission' |
| 3 | import { useAppStore } from '@/store/modules/app' | 3 | import { useAppStore } from '@/store/modules/app' |
| 4 | -import { computed, unref, defineComponent, watch, ref } from 'vue' | 4 | +import { computed, unref, defineComponent, watch, ref, onMounted } from 'vue' |
| 5 | import { useI18n } from '@/hooks/web/useI18n' | 5 | import { useI18n } from '@/hooks/web/useI18n' |
| 6 | import { ElScrollbar } from 'element-plus' | 6 | import { ElScrollbar } from 'element-plus' |
| 7 | import { Icon } from '@/components/Icon' | 7 | import { Icon } from '@/components/Icon' |
| @@ -28,6 +28,8 @@ export default defineComponent({ | @@ -28,6 +28,8 @@ export default defineComponent({ | ||
| 28 | 28 | ||
| 29 | const collapse = computed(() => appStore.getCollapse) | 29 | const collapse = computed(() => appStore.getCollapse) |
| 30 | 30 | ||
| 31 | + const fixedMenu = computed(() => appStore.getFixedMenu) | ||
| 32 | + | ||
| 31 | const permissionStore = usePermissionStore() | 33 | const permissionStore = usePermissionStore() |
| 32 | 34 | ||
| 33 | const routers = computed(() => permissionStore.getRouters) | 35 | const routers = computed(() => permissionStore.getRouters) |
| @@ -38,6 +40,27 @@ export default defineComponent({ | @@ -38,6 +40,27 @@ export default defineComponent({ | ||
| 38 | appStore.setCollapse(!unref(collapse)) | 40 | appStore.setCollapse(!unref(collapse)) |
| 39 | } | 41 | } |
| 40 | 42 | ||
| 43 | + onMounted(() => { | ||
| 44 | + if (unref(fixedMenu)) { | ||
| 45 | + const path = `/${unref(currentRoute).path.split('/')[1]}` | ||
| 46 | + const children = unref(tabRouters).find( | ||
| 47 | + (v) => | ||
| 48 | + (v.meta?.alwaysShow || (v?.children?.length && v?.children?.length > 1)) && | ||
| 49 | + v.path === path | ||
| 50 | + )?.children | ||
| 51 | + | ||
| 52 | + tabActive.value = path | ||
| 53 | + if (children) { | ||
| 54 | + permissionStore.setMenuTabRouters( | ||
| 55 | + cloneDeep(children).map((v) => { | ||
| 56 | + v.path = pathResolve(unref(tabActive), v.path) | ||
| 57 | + return v | ||
| 58 | + }) | ||
| 59 | + ) | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + }) | ||
| 63 | + | ||
| 41 | watch( | 64 | watch( |
| 42 | () => routers.value, | 65 | () => routers.value, |
| 43 | (routers: AppRouteRecordRaw[]) => { | 66 | (routers: AppRouteRecordRaw[]) => { |
| @@ -66,7 +89,7 @@ export default defineComponent({ | @@ -66,7 +89,7 @@ export default defineComponent({ | ||
| 66 | ) | 89 | ) |
| 67 | 90 | ||
| 68 | // 是否显示菜单 | 91 | // 是否显示菜单 |
| 69 | - const showMenu = ref(false) | 92 | + const showMenu = ref(unref(fixedMenu) ? true : false) |
| 70 | 93 | ||
| 71 | // tab高亮 | 94 | // tab高亮 |
| 72 | const tabActive = ref('') | 95 | const tabActive = ref('') |
| @@ -77,9 +100,13 @@ export default defineComponent({ | @@ -77,9 +100,13 @@ export default defineComponent({ | ||
| 77 | window.open(item.path) | 100 | window.open(item.path) |
| 78 | return | 101 | return |
| 79 | } | 102 | } |
| 103 | + const newPath = item.children ? item.path : item.path.split('/')[0] | ||
| 104 | + const oldPath = unref(tabActive) | ||
| 80 | tabActive.value = item.children ? item.path : item.path.split('/')[0] | 105 | tabActive.value = item.children ? item.path : item.path.split('/')[0] |
| 81 | if (item.children) { | 106 | if (item.children) { |
| 82 | - showMenu.value = !unref(showMenu) | 107 | + if (newPath === oldPath || !unref(showMenu)) { |
| 108 | + showMenu.value = unref(fixedMenu) ? true : !unref(showMenu) | ||
| 109 | + } | ||
| 83 | if (unref(showMenu)) { | 110 | if (unref(showMenu)) { |
| 84 | permissionStore.setMenuTabRouters( | 111 | permissionStore.setMenuTabRouters( |
| 85 | cloneDeep(item.children).map((v) => { | 112 | cloneDeep(item.children).map((v) => { |
| @@ -96,7 +123,7 @@ export default defineComponent({ | @@ -96,7 +123,7 @@ export default defineComponent({ | ||
| 96 | } | 123 | } |
| 97 | 124 | ||
| 98 | // 设置高亮 | 125 | // 设置高亮 |
| 99 | - const isActice = (currentPath: string) => { | 126 | + const isActive = (currentPath: string) => { |
| 100 | const { path } = unref(currentRoute) | 127 | const { path } = unref(currentRoute) |
| 101 | if (tabPathMap[currentPath].includes(path)) { | 128 | if (tabPathMap[currentPath].includes(path)) { |
| 102 | return true | 129 | return true |
| @@ -105,7 +132,7 @@ export default defineComponent({ | @@ -105,7 +132,7 @@ export default defineComponent({ | ||
| 105 | } | 132 | } |
| 106 | 133 | ||
| 107 | const mouseleave = () => { | 134 | const mouseleave = () => { |
| 108 | - if (!unref(showMenu)) return | 135 | + if (!unref(showMenu) || unref(fixedMenu)) return |
| 109 | showMenu.value = false | 136 | showMenu.value = false |
| 110 | } | 137 | } |
| 111 | 138 | ||
| @@ -114,7 +141,7 @@ export default defineComponent({ | @@ -114,7 +141,7 @@ export default defineComponent({ | ||
| 114 | id={`${variables.namespace}-menu`} | 141 | id={`${variables.namespace}-menu`} |
| 115 | class={[ | 142 | class={[ |
| 116 | prefixCls, | 143 | prefixCls, |
| 117 | - 'relative bg-[var(--left-menu-bg-color)] top-1px z-999', | 144 | + 'relative bg-[var(--left-menu-bg-color)] top-1px z-3000', |
| 118 | { | 145 | { |
| 119 | 'w-[var(--tab-menu-max-width)]': !unref(collapse), | 146 | 'w-[var(--tab-menu-max-width)]': !unref(collapse), |
| 120 | 'w-[var(--tab-menu-min-width)]': unref(collapse) | 147 | 'w-[var(--tab-menu-min-width)]': unref(collapse) |
| @@ -140,7 +167,7 @@ export default defineComponent({ | @@ -140,7 +167,7 @@ export default defineComponent({ | ||
| 140 | `${prefixCls}__item`, | 167 | `${prefixCls}__item`, |
| 141 | 'text-center text-12px relative py-12px cursor-pointer', | 168 | 'text-center text-12px relative py-12px cursor-pointer', |
| 142 | { | 169 | { |
| 143 | - 'is-active': isActice(v.path) | 170 | + 'is-active': isActive(v.path) |
| 144 | } | 171 | } |
| 145 | ]} | 172 | ]} |
| 146 | onClick={() => { | 173 | onClick={() => { |
| @@ -174,8 +201,8 @@ export default defineComponent({ | @@ -174,8 +201,8 @@ export default defineComponent({ | ||
| 174 | { | 201 | { |
| 175 | '!left-[var(--tab-menu-min-width)]': unref(collapse), | 202 | '!left-[var(--tab-menu-min-width)]': unref(collapse), |
| 176 | '!left-[var(--tab-menu-max-width)]': !unref(collapse), | 203 | '!left-[var(--tab-menu-max-width)]': !unref(collapse), |
| 177 | - '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu), | ||
| 178 | - '!w-0': !unref(showMenu) | 204 | + '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu), |
| 205 | + '!w-0': !unref(showMenu) && !unref(fixedMenu) | ||
| 179 | } | 206 | } |
| 180 | ]} | 207 | ]} |
| 181 | style="transition: width var(--transition-time-02), left var(--transition-time-02);" | 208 | style="transition: width var(--transition-time-02), left var(--transition-time-02);" |
yudao-ui-admin-vue3/src/components/TabMenu/src/helper.ts
| @@ -28,9 +28,9 @@ export const filterMenusPath = ( | @@ -28,9 +28,9 @@ export const filterMenusPath = ( | ||
| 28 | let data: Nullable<AppRouteRecordRaw> = null | 28 | let data: Nullable<AppRouteRecordRaw> = null |
| 29 | const meta = (v.meta ?? {}) as RouteMeta | 29 | const meta = (v.meta ?? {}) as RouteMeta |
| 30 | if (!meta.hidden || meta.canTo) { | 30 | if (!meta.hidden || meta.canTo) { |
| 31 | - const allParentPaht = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path) | 31 | + const allParentPath = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path) |
| 32 | 32 | ||
| 33 | - const fullPath = isUrl(v.path) ? v.path : allParentPaht.join('/') | 33 | + const fullPath = isUrl(v.path) ? v.path : allParentPath.join('/') |
| 34 | 34 | ||
| 35 | data = cloneDeep(v) | 35 | data = cloneDeep(v) |
| 36 | data.path = fullPath | 36 | data.path = fullPath |
| @@ -42,8 +42,8 @@ export const filterMenusPath = ( | @@ -42,8 +42,8 @@ export const filterMenusPath = ( | ||
| 42 | res.push(data) | 42 | res.push(data) |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | - if (allParentPaht.length && Reflect.has(tabPathMap, allParentPaht[0])) { | ||
| 46 | - tabPathMap[allParentPaht[0]].push(fullPath) | 45 | + if (allParentPath.length && Reflect.has(tabPathMap, allParentPath[0])) { |
| 46 | + tabPathMap[allParentPath[0]].push(fullPath) | ||
| 47 | } | 47 | } |
| 48 | } | 48 | } |
| 49 | } | 49 | } |
yudao-ui-admin-vue3/src/components/Table/index.ts
| 1 | import Table from './src/Table.vue' | 1 | import Table from './src/Table.vue' |
| 2 | +import { ElTable } from 'element-plus' | ||
| 3 | +import { TableSetPropsType } from '@/types/table' | ||
| 2 | 4 | ||
| 3 | export interface TableExpose { | 5 | export interface TableExpose { |
| 4 | setProps: (props: Recordable) => void | 6 | setProps: (props: Recordable) => void |
| 5 | setColumn: (columnProps: TableSetPropsType[]) => void | 7 | setColumn: (columnProps: TableSetPropsType[]) => void |
| 6 | selections: Recordable[] | 8 | selections: Recordable[] |
| 9 | + elTableRef: ComponentRef<typeof ElTable> | ||
| 7 | } | 10 | } |
| 8 | 11 | ||
| 9 | export { Table } | 12 | export { Table } |
yudao-ui-admin-vue3/src/components/Table/src/Table.vue
| @@ -6,6 +6,7 @@ import { setIndex } from './helper' | @@ -6,6 +6,7 @@ import { setIndex } from './helper' | ||
| 6 | import { getSlot } from '@/utils/tsxHelper' | 6 | import { getSlot } from '@/utils/tsxHelper' |
| 7 | import type { TableProps } from './types' | 7 | import type { TableProps } from './types' |
| 8 | import { set } from 'lodash-es' | 8 | import { set } from 'lodash-es' |
| 9 | +import { Pagination, TableColumn, TableSetPropsType, TableSlotDefault } from '@/types/table' | ||
| 9 | 10 | ||
| 10 | export default defineComponent({ | 11 | export default defineComponent({ |
| 11 | name: 'Table', | 12 | name: 'Table', |
yudao-ui-admin-vue3/src/components/Table/src/types.ts
yudao-ui-admin-vue3/src/components/UserInfo/src/UserInfo.vue
| @@ -36,8 +36,8 @@ const loginOut = () => { | @@ -36,8 +36,8 @@ const loginOut = () => { | ||
| 36 | }) | 36 | }) |
| 37 | .then(async () => { | 37 | .then(async () => { |
| 38 | userStore.loginOut() | 38 | userStore.loginOut() |
| 39 | - tagsViewStore.delAllViews | ||
| 40 | - replace('/login') | 39 | + tagsViewStore.delAllViews() |
| 40 | + replace('/login?redirect=/index') | ||
| 41 | }) | 41 | }) |
| 42 | .catch(() => {}) | 42 | .catch(() => {}) |
| 43 | } | 43 | } |