浏览代码

Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/1.8.0-uniapp

 Conflicts:
	sql/optional/mall/mall.sql
YunaiV 2 年之前
父节点
当前提交
833fd33844
共有 100 个文件被更改,包括 1380 次插入701 次删除
  1. 55 37
      README.md
  2. 1 1
      pom.xml
  3. 0 5
      sql/mysql/ruoyi-vue-pro.sql
  4. 7 0
      sql/mysql/update.sql
  5. 2 0
      sql/mysql/vue3-menu.sql
  6. 142 142
      sql/optional/mall/mall.sql
  7. 25 7
      yudao-dependencies/pom.xml
  8. 1 0
      yudao-framework/pom.xml
  9. 2 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java
  10. 4 5
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
  11. 4 4
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java
  12. 8 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml
  13. 3 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
  14. 1 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java
  15. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml
  16. 39 0
      yudao-framework/yudao-spring-boot-starter-captcha/pom.xml
  17. 25 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/config/YudaoCaptchaConfiguration.java
  18. 28 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/enums/CaptchaRedisKeyConstants.java
  19. 54 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/service/RedisCaptchaServiceImpl.java
  20. 7 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/package-info.java
  21. 1 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService
  22. 2 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring.factories
  23. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg1.png
  24. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg2.png
  25. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg3.png
  26. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg4.png
  27. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg5.png
  28. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg6.png
  29. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg7.png
  30. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg8.png
  31. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg9.png
  32. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/1.png
  33. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/10.png
  34. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/11.png
  35. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/12.png
  36. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/13.png
  37. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/14.png
  38. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/15.png
  39. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/16.png
  40. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/17.png
  41. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/18.png
  42. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/19.png
  43. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/8.png
  44. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/9.png
  45. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/2.png
  46. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/3.png
  47. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/4.png
  48. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg1.png
  49. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg10.png
  50. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg2.png
  51. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg3.png
  52. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg4.png
  53. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg5.png
  54. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg6.png
  55. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg7.png
  56. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg8.png
  57. 二进制
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg9.png
  58. 2 2
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java
  59. 4 4
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyRegistry.java
  60. 17 15
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
  61. 5 5
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java
  62. 1 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
  63. 2 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java
  64. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java
  65. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm
  66. 6 6
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm
  67. 3 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
  68. 1 1
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java
  69. 1 2
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  70. 5 0
      yudao-module-system/yudao-module-system-biz/pom.xml
  71. 0 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  72. 4 6
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java
  73. 0 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.http
  74. 0 32
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.java
  75. 0 27
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java
  76. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java
  77. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java
  78. 0 17
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/common/CaptchaConvert.java
  79. 0 9
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaConfig.java
  80. 0 38
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaProperties.java
  81. 0 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/package-info.java
  82. 32 33
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
  83. 0 39
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaService.java
  84. 0 65
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java
  85. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
  86. 77 83
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java
  87. 0 65
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceTest.java
  88. 5 0
      yudao-module-visualization/yudao-module-visualization-biz/pom.xml
  89. 7 0
      yudao-server/src/main/resources/application-dev.yaml
  90. 12 12
      yudao-server/src/main/resources/application-local.yaml
  91. 23 6
      yudao-server/src/main/resources/application.yaml
  92. 6 1
      yudao-server/src/test/java/cn/iocoder/yudao/ProjectReactor.java
  93. 16 0
      yudao-ui-admin-uniapp/.gitignore
  94. 34 0
      yudao-ui-admin-uniapp/App.vue
  95. 21 0
      yudao-ui-admin-uniapp/LICENSE
  96. 60 0
      yudao-ui-admin-uniapp/api/login.js
  97. 42 0
      yudao-ui-admin-uniapp/api/system/user.js
  98. 167 0
      yudao-ui-admin-uniapp/components/uni-section/uni-section.vue
  99. 391 0
      yudao-ui-admin-uniapp/components/verifition/Verify.vue
  100. 14 0
      yudao-ui-admin-uniapp/components/verifition/utils/ase.js

+ 55 - 37
README.md

@@ -23,8 +23,8 @@
 >
 > 😜 给项目点点 Star 吧,这对我们真的很重要!
 
-* 前端 Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
-* 前端 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin)
+* 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 
+* 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
 * 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson
 * 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
 * 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
@@ -156,8 +156,9 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | `yudao-dependencies`  | Maven 依赖版本管理       |
 | `yudao-framework`     | Java 框架拓展          |
 | `yudao-server`        | 管理后台 + 用户 APP 的服务端 |
-| `yudao-ui-admin`      | Vue2 管理后台的 UI 界面        |
-| `yudao-ui-admin-vue3`      | Vue3 管理后台的 UI 界面        |
+| `yudao-ui-admin`      | 管理后台的 Vue2 前端项目        |
+| `yudao-ui-admin-vue3`      | 管理后台的 Vue3 前端项目        |
+| `yudao-ui-admin-uniapp`      | 管理后台的 uni-app 多端项目        |
 | `yudao-ui-app`       | 用户 APP 的 UI 界面     |
 | `yudao-module-system` | 系统功能的 Module 模块    |
 | `yudao-module-member` | 会员中心的 Module 模块    |
@@ -169,48 +170,55 @@ ps:核心功能已经实现,正在对接微信小程序中...
 ### 后端
 
 | 框架                                                                                         | 说明                   | 版本      | 学习指南                                                           |
-|---------------------------------------------------------------------------------------------|-----------------------|-----------|----------------------------------------------------------------|
-| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架             | 2.6.10    | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
-| [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器             | 5.7      |                                                                |
-| [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件     | 1.2.11    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
-| [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包       | 3.5.2    | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
-| [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源               | 3.5.0    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
-| [Redis](https://redis.io/)                                                                  | key-value 数据库        | 5.0      |                                                                |
-| [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端            | 3.17.4   | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao)           |
-| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架               | 5.3.20    | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao)               |
-| [Spring Security](https://github.com/spring-projects/spring-security)                       | Spring 安全框架         | 5.6.5    | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
-| [Hibernate Validator](https://github.com/hibernate/hibernate-validator)                     | 参数校验组件             | 6.2.3    | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao)      |
-| [Flowable](https://github.com/flowable/flowable-engine)                                     | 工作流引擎               | 6.7.0    | [文档](https://doc.iocoder.cn/bpm/)                                                     |
-| [Quartz](https://github.com/quartz-scheduler)                                               | 任务调度组件             | 2.3.2    | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao)             |
-| [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现    | 3.0.3    | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
-| [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件             | 1.7.1    | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
-| [SkyWalking](https://skywalking.apache.org/)                                                | 分布式应用追踪系统        | 8.5.0    | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao)      |
-| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin)                       | Spring Boot 监控平台    | 2.6.7    | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao)           |
-| [Jackson](https://github.com/FasterXML/jackson)                                             | JSON 工具库             | 2.13.3   |                                                                |
-| [MapStruct](https://mapstruct.org/)                                                         | Java Bean 转换         | 1.4.1    | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao)       |
-| [Lombok](https://projectlombok.org/)                                                        | 消除冗长的 Java 代码     | 1.16.14  | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao)          |
-| [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架        | 5.8.2    | -                                                              |
-| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架         | 4.0.0    | -                                                              |
-
-### Vue2 前端
+|---------------------------------------------------------------------------------------------|-----------------------|---------|----------------------------------------------------------------|
+| [Spring Boot](https://spring.io/projects/spring-boot)                                       | 应用开发框架             | 2.6.10  | [文档](https://github.com/YunaiV/SpringBoot-Labs)                |
+| [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器             | 5.7     |                                                                |
+| [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件     | 1.2.11  | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
+| [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包       | 3.5.2   | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
+| [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源               | 3.5.0   | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
+| [Redis](https://redis.io/)                                                                  | key-value 数据库        | 5.0     |                                                                |
+| [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端            | 3.17.4  | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao)           |
+| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架               | 5.3.20  | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao)               |
+| [Spring Security](https://github.com/spring-projects/spring-security)                       | Spring 安全框架         | 5.6.5   | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
+| [Hibernate Validator](https://github.com/hibernate/hibernate-validator)                     | 参数校验组件             | 6.2.3   | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao)      |
+| [Flowable](https://github.com/flowable/flowable-engine)                                     | 工作流引擎               | 6.7.2   | [文档](https://doc.iocoder.cn/bpm/)                                                     |
+| [Quartz](https://github.com/quartz-scheduler)                                               | 任务调度组件             | 2.3.2   | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao)             |
+| [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现    | 3.0.3   | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
+| [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件             | 1.7.1   | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
+| [SkyWalking](https://skywalking.apache.org/)                                                | 分布式应用追踪系统        | 8.5.0   | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao)      |
+| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin)                       | Spring Boot 监控平台    | 2.6.7   | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao)           |
+| [Jackson](https://github.com/FasterXML/jackson)                                             | JSON 工具库             | 2.13.3  |                                                                |
+| [MapStruct](https://mapstruct.org/)                                                         | Java Bean 转换         | 1.4.1   | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao)       |
+| [Lombok](https://projectlombok.org/)                                                        | 消除冗长的 Java 代码     | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao)          |
+| [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架        | 5.8.2   | -                                                              |
+| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架         | 4.0.0   | -                                                              |
+
+### [管理后台 Vue2 前端](./yudao-ui-admin)
 
 | 框架                                                                           | 说明            | 版本     |
 |------------------------------------------------------------------------------|---------------|--------|
 | [Vue](https://cn.vuejs.org/index.html)                                       | JavaScript 框架 | 2.6.12 |
 | [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案      | -      |
 
-### Vue3 前端
+### [管理后台 Vue3 前端](./yudao-ui-admin-vue3)
+
+| 框架                                                                  | 说明              | 版本     |
+|----------------------------------------------------------------------|-----------------|--------|
+| [Vue](https://staging-cn.vuejs.org/)                                 | Vue 框架          | 3.2.37 |
+| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具         | 3.0.4  |
+| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus    | 2.2.12 |
+| [TypeScript](https://www.typescriptlang.org/docs/)                   | TypeScript      | 4.7.4  |
+| [pinia](https://pinia.vuejs.org/)                                    | vuex5           | 2.0.17 |
+| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化             | 9.2.2  |
+| [windicss](https://cn.windicss.org/)                                 | 下一代工具优先的 CSS 框架 | 3.5.6  |
+| [iconify](https://icon-sets.iconify.design/)                         | 在线图标库           | 2.2.1  |
+
+### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
 
 | 框架                                                                  | 说明               | 版本     |
 |----------------------------------------------------------------------|------------------|--------|
-| [Vue](https://staging-cn.vuejs.org/)                                 | Vue 框架           | 3.2.37 |
-| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具          | 3.0.3  |
-| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus     | 2.2.11 |
-| [TypeScript](https://www.typescriptlang.org/docs/)                   | TypeScript       | 4.7.4  |
-| [pinia](https://pinia.vuejs.org/)                                    | Vue 存储库 替代 vuex5 | 2.0.17 |
-| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化              | 9.1.10 |
-| [windicss](https://cn.windicss.org/)                                 | 下一代工具优先的 CSS 框架  | 3.5.6  |
-| [iconify](https://icon-sets.iconify.design/)                         | 在线图标库            | 2.2.1  |
+| [uni-app](hhttps://github.com/dcloudio/uni-app)                                 | 跨平台框架           | 2.0.0 |
+| [uni-ui](https://github.com/dcloudio/uni-ui)                                      | 基于 uni-app 的 UI 框架          | 1.4.20  |
 
 ## 🐷 演示图
 
@@ -262,3 +270,13 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 模块      | biu                                                              | biu                                                                    | biu                                                                    |
 |---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
 | 报表设计器 | ![数据报表](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-数据报表.jpg?imageView2/2/format/webp/w/1280) | ![图形报表](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-图形报表.jpg?imageView2/2/format/webp/w/1280) | ![报表设计器-打印设计](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-打印设计.jpg?imageView2/2/format/webp/w/1280) |
+
+### 移动端(管理后台)
+
+| biu                                                              | biu                                                                    | biu                                                                    |
+|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
+| ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/01.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/02.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/03.png?imageView2/2/format/webp) |
+| ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/04.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/05.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/06.png?imageView2/2/format/webp) |
+| ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/07.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/08.png?imageView2/2/format/webp) | ![](https://static.iocoder.cn/images/ruoyi-vue-pro/admin-uniapp/09.png?imageView2/2/format/webp) |
+
+目前已经实现登录、我的、工作台、编辑资料、头像修改、密码修改、常见问题、关于我们等基础功能。

+ 1 - 1
pom.xml

@@ -27,7 +27,7 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.6.3-snapshot</revision>
+        <revision>1.6.4-snapshot</revision>
         <!-- Maven 相关 -->
         <java.version>1.8</java.version>
         <maven.compiler.source>${java.version}</maven.compiler.source>

+ 0 - 5
sql/mysql/ruoyi-vue-pro.sql

@@ -2657,8 +2657,3 @@ INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`,
 COMMIT;
 
 SET FOREIGN_KEY_CHECKS = 1;
-
-
--- 积木报表菜单
-INSERT INTO `system_menu` VALUES (1281, '可视化报表', '', 1, 12, 0, '/visualization', 'chart', NULL, 0, b'1', b'1', '1', '2022-07-10 20:22:15', '1', '2022-07-10 20:33:30', b'0');
-INSERT INTO `system_menu` VALUES (1282, '积木报表', '', 2, 1, 1281, 'jm-report', '#', 'visualization/jm/index', 0, b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2022-07-10 20:33:26', b'0');

+ 7 - 0
sql/mysql/update.sql

@@ -0,0 +1,7 @@
+-- ----------------------------
+-- 升级SQL文件,全新安装只需要执行ruoyi-vue-pro.sql文件即可
+-- 1.6.2 ==> 1.6.3
+-- ----------------------------
+-- 积木报表菜单
+INSERT INTO `system_menu` VALUES (1281, '可视化报表', '', 1, 12, 0, '/visualization', 'chart', NULL, 0, b'1', b'1', '1', '2022-07-10 20:22:15', '1', '2022-07-10 20:33:30', b'0');
+INSERT INTO `system_menu` VALUES (1282, '积木报表', '', 2, 1, 1281, 'jm-report', '#', 'visualization/jm/index', 0, b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2022-07-10 20:33:26', b'0');

+ 2 - 0
sql/mysql/vue3-menu.sql

@@ -257,5 +257,7 @@ INSERT INTO `system_menu` VALUES (1264, '客户端查询', 'system:oauth2-client
 INSERT INTO `system_menu` VALUES (1265, '客户端创建', 'system:oauth2-client:create', 3, 2, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:23', b'0');
 INSERT INTO `system_menu` VALUES (1266, '客户端更新', 'system:oauth2-client:update', 3, 3, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:28', b'0');
 INSERT INTO `system_menu` VALUES (1267, '客户端删除', 'system:oauth2-client:delete', 3, 4, 1263, '', '', '', 0, b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:33', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1281, '可视化报表', '', 1, 12, 0, '/visualization', 'ep:histogram', NULL, 0, b'1', b'1', '1', '2022-07-10 20:22:15', '1', '2022-07-10 20:33:30', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1282, '积木报表', '', 2, 1, 1281, 'jimu-report', 'ep:histogram', 'visualization/jmreport/index', 0, b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2022-07-28 21:17:34', b'0');
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 142 - 142
sql/optional/mall/mall.sql

@@ -22,23 +22,23 @@ SET FOREIGN_KEY_CHECKS = 0;
 -- ----------------------------
 DROP TABLE IF EXISTS `market_activity`;
 CREATE TABLE `market_activity`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '活动编号',
-  `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '活动标题',
-  `activity_type` tinyint NOT NULL COMMENT '活动类型',
-  `status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
-  `start_time` datetime NOT NULL COMMENT '开始时间',
-  `end_time` datetime NOT NULL COMMENT '结束时间',
-  `invalid_time` datetime NULL DEFAULT NULL COMMENT '失效时间',
-  `delete_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
-  `time_limited_discount` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
-  `full_privilege` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
-  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
-  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
-  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
-  PRIMARY KEY (`id`) USING BTREE
+                                    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '活动编号',
+                                    `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '活动标题',
+                                    `activity_type` tinyint NOT NULL COMMENT '活动类型',
+                                    `status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
+                                    `start_time` datetime NOT NULL COMMENT '开始时间',
+                                    `end_time` datetime NOT NULL COMMENT '结束时间',
+                                    `invalid_time` datetime NULL DEFAULT NULL COMMENT '失效时间',
+                                    `delete_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
+                                    `time_limited_discount` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
+                                    `full_privilege` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
+                                    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
+                                    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
+                                    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                    `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                    PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '促销活动';
 
 -- ----------------------------
@@ -52,20 +52,20 @@ COMMIT;
 -- ----------------------------
 DROP TABLE IF EXISTS `market_banner`;
 CREATE TABLE `market_banner`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Banner编号',
-  `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT 'Banner标题',
-  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图片URL',
-  `status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
-  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '跳转地址',
-  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
-  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
-  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
-  `sort` tinyint NULL DEFAULT NULL COMMENT '排序',
-  `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述',
-  PRIMARY KEY (`id`) USING BTREE
+                                  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Banner编号',
+                                  `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT 'Banner标题',
+                                  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图片URL',
+                                  `status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
+                                  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '跳转地址',
+                                  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
+                                  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
+                                  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                  `sort` tinyint NULL DEFAULT NULL COMMENT '排序',
+                                  `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述',
+                                  PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'Banner管理';
 
 -- ----------------------------
@@ -79,22 +79,22 @@ COMMIT;
 -- ----------------------------
 DROP TABLE IF EXISTS `member_address`;
 CREATE TABLE `member_address`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '收件地址编号',
-  `user_id` bigint NOT NULL COMMENT '用户编号',
-  `name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '收件人名称',
-  `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '手机号',
-  `area_id` bigint NOT NULL COMMENT '地区编码',
-  `post_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '邮编',
-  `detail_address` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '收件详细地址',
-  `defaulted` bit(1) NOT NULL COMMENT '是否默认',
-  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
-  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
-  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
-  PRIMARY KEY (`id`) USING BTREE,
-  INDEX `idx_userId`(`user_id` ASC) USING BTREE
+                                   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '收件地址编号',
+                                   `user_id` bigint NOT NULL COMMENT '用户编号',
+                                   `name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '收件人名称',
+                                   `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '手机号',
+                                   `area_id` bigint NOT NULL COMMENT '地区编码',
+                                   `post_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '邮编',
+                                   `detail_address` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '收件详细地址',
+                                   `defaulted` bit(1) NOT NULL COMMENT '是否默认',
+                                   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+                                   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                   `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+                                   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                   PRIMARY KEY (`id`) USING BTREE,
+                                   INDEX `idx_userId`(`user_id` ASC) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
 
 -- ----------------------------
@@ -109,19 +109,19 @@ COMMIT;
 -- ----------------------------
 DROP TABLE IF EXISTS `product_brand`;
 CREATE TABLE `product_brand`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '品牌编号',
-  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌名称',
-  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌图片',
-  `sort` int NULL DEFAULT 0 COMMENT '品牌排序',
-  `description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '品牌描述',
-  `status` tinyint NOT NULL COMMENT '状态',
-  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
-  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
-  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
-  PRIMARY KEY (`id`) USING BTREE
+                                  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '品牌编号',
+                                  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌名称',
+                                  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌图片',
+                                  `sort` int NULL DEFAULT 0 COMMENT '品牌排序',
+                                  `description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '品牌描述',
+                                  `status` tinyint NOT NULL COMMENT '状态',
+                                  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+                                  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+                                  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                  PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品品牌';
 
 -- ----------------------------
@@ -136,20 +136,20 @@ COMMIT;
 -- ----------------------------
 DROP TABLE IF EXISTS `product_category`;
 CREATE TABLE `product_category`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号',
-  `parent_id` bigint NOT NULL COMMENT '父分类编号',
-  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
-  `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类图片',
-  `sort` int NULL DEFAULT 0 COMMENT '分类排序',
-  `description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '分类描述',
-  `status` tinyint NOT NULL COMMENT '开启状态',
-  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
-  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
-  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
-  PRIMARY KEY (`id`) USING BTREE
+                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号',
+                                     `parent_id` bigint NOT NULL COMMENT '父分类编号',
+                                     `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
+                                     `pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类图片',
+                                     `sort` int NULL DEFAULT 0 COMMENT '分类排序',
+                                     `description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '分类描述',
+                                     `status` tinyint NOT NULL COMMENT '开启状态',
+                                     `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+                                     `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                     `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+                                     `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                     `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                     `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                     PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品分类';
 
 -- ----------------------------
@@ -164,17 +164,17 @@ COMMIT;
 -- ----------------------------
 DROP TABLE IF EXISTS `product_property`;
 CREATE TABLE `product_property`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
-  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格名称',
-  `status` tinyint NULL DEFAULT NULL COMMENT '状态: 0 开启 ,1 禁用',
-  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
-  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
-  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-  PRIMARY KEY (`id`) USING BTREE,
-  INDEX `idx_name`(`name`(32) ASC) USING BTREE COMMENT '规格名称索引'
+                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                     `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格名称',
+                                     `status` tinyint NULL DEFAULT NULL COMMENT '状态: 0 开启 ,1 禁用',
+                                     `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                     `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                     `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
+                                     `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
+                                     `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                     `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                     PRIMARY KEY (`id`) USING BTREE,
+                                     INDEX `idx_name`(`name`(32) ASC) USING BTREE COMMENT '规格名称索引'
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格名称';
 
 -- ----------------------------
@@ -188,17 +188,17 @@ COMMIT;
 -- ----------------------------
 DROP TABLE IF EXISTS `product_property_value`;
 CREATE TABLE `product_property_value`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
-  `property_id` bigint NULL DEFAULT NULL COMMENT '规格键id',
-  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格值名字',
-  `status` tinyint NULL DEFAULT NULL COMMENT '状态: 1 开启 ,2 禁用',
-  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
-  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
-  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-  PRIMARY KEY (`id`) USING BTREE
+                                           `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                           `property_id` bigint NULL DEFAULT NULL COMMENT '规格键id',
+                                           `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格值名字',
+                                           `status` tinyint NULL DEFAULT NULL COMMENT '状态: 1 开启 ,2 禁用',
+                                           `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                           `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                           `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
+                                           `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
+                                           `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+                                           `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                           PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格值';
 
 -- ----------------------------
@@ -212,25 +212,25 @@ COMMIT;
 -- ----------------------------
 DROP TABLE IF EXISTS `product_sku`;
 CREATE TABLE `product_sku` (
-`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
-`spu_id` bigint NOT NULL COMMENT 'spu编号',
-`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
-`name` varchar(128)  DEFAULT NULL COMMENT '商品 SKU 名字',
-`properties` varchar(128)  DEFAULT NULL COMMENT '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
-`price` int NOT NULL DEFAULT '-1' COMMENT '销售价格,单位:分',
-`market_price` int DEFAULT NULL COMMENT '市场价',
-`cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',
-`pic_url` varchar(128)  NOT NULL COMMENT '图片地址',
-`stock` int DEFAULT NULL COMMENT '库存',
-`warn_stock` int DEFAULT NULL COMMENT '预警库存',
-`volume` double DEFAULT NULL COMMENT '商品体积',
-`weight` double DEFAULT NULL COMMENT '商品重量',
-`bar_code` varchar(64)  DEFAULT NULL COMMENT '条形码',
-`status` tinyint DEFAULT NULL COMMENT '状态: 0-正常 1-禁用',
-`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-`creator` varchar(64) DEFAULT NULL COMMENT '创建人',
-`updater` double(64,0) DEFAULT NULL COMMENT '更新人',
+                               `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+                               `spu_id` bigint NOT NULL COMMENT 'spu编号',
+                               `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
+                               `name` varchar(128)  DEFAULT NULL COMMENT '商品 SKU 名字',
+                               `properties` varchar(128)  DEFAULT NULL COMMENT '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
+                               `price` int NOT NULL DEFAULT '-1' COMMENT '销售价格,单位:分',
+                               `market_price` int DEFAULT NULL COMMENT '市场价',
+                               `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',
+                               `pic_url` varchar(128)  NOT NULL COMMENT '图片地址',
+                               `stock` int DEFAULT NULL COMMENT '库存',
+                               `warn_stock` int DEFAULT NULL COMMENT '预警库存',
+                               `volume` double DEFAULT NULL COMMENT '商品体积',
+                               `weight` double DEFAULT NULL COMMENT '商品重量',
+                               `bar_code` varchar(64)  DEFAULT NULL COMMENT '条形码',
+                               `status` tinyint DEFAULT NULL COMMENT '状态: 0-正常 1-禁用',
+                               `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                               `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                               `creator` varchar(64) DEFAULT NULL COMMENT '创建人',
+                               `updater` double(64,0) DEFAULT NULL COMMENT '更新人',
 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
 PRIMARY KEY (`id`) USING BTREE
 ) ENGINE=InnoDB COMMENT='商品sku';
@@ -246,33 +246,33 @@ COMMIT;
 -- ----------------------------
 DROP TABLE IF EXISTS `product_spu`;
 CREATE TABLE `product_spu` (
-`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
-`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
-`brand_id` int DEFAULT NULL COMMENT '商品品牌编号',
-`category_id` bigint NOT NULL COMMENT '分类id',
-`spec_type` int NOT NULL COMMENT '规格类型:0 单规格 1 多规格',
-`code` varchar(128)  DEFAULT NULL COMMENT '商品编码',
-`name` varchar(128)  NOT NULL COMMENT '商品名称',
-`sell_point` varchar(128)  DEFAULT NULL COMMENT '卖点',
-`description` text  COMMENT '描述',
-`pic_urls` varchar(1024)  DEFAULT '' COMMENT '商品轮播图地址\n 数组,以逗号分隔\n 最多上传15张',
-`video_url` varchar(128)  DEFAULT NULL COMMENT '商品视频',
-`market_price` int DEFAULT NULL COMMENT '市场价,单位使用:分',
-`min_price` int DEFAULT NULL COMMENT '最小价格,单位使用:分',
-`max_price` int DEFAULT NULL COMMENT '最大价格,单位使用:分',
-`total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',
-`show_stock` int DEFAULT '0' COMMENT '是否展示库存',
-`sales_count` int DEFAULT '0' COMMENT '商品销量',
-`virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
-`click_count` int DEFAULT '0' COMMENT '商品点击量',
-`status` bit(1) DEFAULT NULL COMMENT '上下架状态: 0 上架(开启) 1 下架(禁用)-1 回收',
-`sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
-`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-`creator` varchar(64)  DEFAULT NULL COMMENT '创建人',
-`updater` varchar(64)  DEFAULT NULL COMMENT '更新人',
-`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-PRIMARY KEY (`id`) USING BTREE
+                               `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+                               `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
+                               `brand_id` int DEFAULT NULL COMMENT '商品品牌编号',
+                               `category_id` bigint NOT NULL COMMENT '分类id',
+                               `spec_type` int NOT NULL COMMENT '规格类型:0 单规格 1 多规格',
+                               `code` varchar(128)  DEFAULT NULL COMMENT '商品编码',
+                               `name` varchar(128)  NOT NULL COMMENT '商品名称',
+                               `sell_point` varchar(128)  DEFAULT NULL COMMENT '卖点',
+                               `description` text  COMMENT '描述',
+                               `pic_urls` varchar(1024)  DEFAULT '' COMMENT '商品轮播图地址\n 数组,以逗号分隔\n 最多上传15张',
+                               `video_url` varchar(128)  DEFAULT NULL COMMENT '商品视频',
+                               `market_price` int DEFAULT NULL COMMENT '市场价,单位使用:分',
+                               `min_price` int DEFAULT NULL COMMENT '最小价格,单位使用:分',
+                               `max_price` int DEFAULT NULL COMMENT '最大价格,单位使用:分',
+                               `total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',
+                               `show_stock` int DEFAULT '0' COMMENT '是否展示库存',
+                               `sales_count` int DEFAULT '0' COMMENT '商品销量',
+                               `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
+                               `click_count` int DEFAULT '0' COMMENT '商品点击量',
+                               `status` bit(1) DEFAULT NULL COMMENT '上下架状态: 0 上架(开启) 1 下架(禁用)-1 回收',
+                               `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
+                               `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                               `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                               `creator` varchar(64)  DEFAULT NULL COMMENT '创建人',
+                               `updater` varchar(64)  DEFAULT NULL COMMENT '更新人',
+                               `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                               PRIMARY KEY (`id`) USING BTREE
 ) ENGINE=InnoDB COMMENT='商品spu';
 
 -- ----------------------------

+ 25 - 7
yudao-dependencies/pom.xml

@@ -14,7 +14,7 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.6.3-snapshot</revision>
+        <revision>1.6.4-snapshot</revision>
         <!-- 统一依赖管理 -->
         <spring.boot.version>2.6.10</spring.boot.version>
         <!-- Web 相关 -->
@@ -41,14 +41,14 @@
         <jedis-mock.version>0.1.16</jedis-mock.version>
         <mockito-inline.version>4.0.0</mockito-inline.version>
         <!-- Bpm 工作流相关 -->
-        <flowable.version>6.7.0</flowable.version>
+        <flowable.version>6.7.2</flowable.version>
         <!-- 工具类相关 -->
         <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version>
         <lombok.version>1.18.20</lombok.version>
         <mapstruct.version>1.4.1.Final</mapstruct.version>
-        <hutool.version>5.7.22</hutool.version>
+        <hutool.version>5.8.5</hutool.version>
         <easyexcel.verion>3.1.1</easyexcel.verion>
-        <velocity.version>2.2</velocity.version>
+        <velocity.version>2.3</velocity.version>
         <screw.version>1.0.5</screw.version>
 		<fastjson.version>1.2.83</fastjson.version>
         <guava.version>30.1.1-jre</guava.version>
@@ -57,14 +57,16 @@
         <commons-net.version>3.8.0</commons-net.version>
         <jsch.version>0.1.55</jsch.version>
         <tika-core.version>2.4.1</tika-core.version>
+        <aj-captcha.version>1.3.0</aj-captcha.version>
         <!-- 三方云服务相关 -->
         <minio.version>8.2.2</minio.version>
-        <aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version>
-        <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
-        <tencentcloud-sdk-java.version>3.1.471</tencentcloud-sdk-java.version>
+        <aliyun-java-sdk-core.version>4.6.0</aliyun-java-sdk-core.version>
+        <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
+        <tencentcloud-sdk-java.version>3.1.561</tencentcloud-sdk-java.version>
         <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
         <justauth.version>1.4.0</justauth.version>
         <jimureport.version>1.5.2</jimureport.version>
+        <xercesImpl.version>2.12.0</xercesImpl.version>
     </properties>
 
     <dependencyManagement>
@@ -129,6 +131,11 @@
                 <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
                 <version>${revision}</version>
             </dependency>
+            <dependency>
+                <groupId>cn.iocoder.boot</groupId>
+                <artifactId>yudao-spring-boot-starter-captcha</artifactId>
+                <version>${revision}</version>
+            </dependency>
 
             <!-- Spring 核心 -->
             <dependency>
@@ -451,6 +458,12 @@
                 <version>${tika-core.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.anji-plus</groupId>
+                <artifactId>spring-boot-starter-captcha</artifactId>
+                <version>${aj-captcha.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.apache.velocity</groupId>
                 <artifactId>velocity-engine-core</artifactId>
@@ -565,6 +578,11 @@
                 <artifactId>jimureport-spring-boot-starter</artifactId>
                 <version>${jimureport.version}</version>
             </dependency>
+            <dependency>
+                <groupId>xerces</groupId>
+                <artifactId>xercesImpl</artifactId>
+                <version>${xercesImpl.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

+ 1 - 0
yudao-framework/pom.xml

@@ -39,6 +39,7 @@
         <module>yudao-spring-boot-starter-biz-error-code</module>
 
         <module>yudao-spring-boot-starter-flowable</module>
+        <module>yudao-spring-boot-starter-captcha</module>
     </modules>
 
     <artifactId>yudao-framework</artifactId>

+ 2 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.framework.common.util.collection;
 
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.collection.IterUtil;
 import cn.hutool.core.util.ArrayUtil;
 
 import java.util.Collection;
@@ -44,7 +45,7 @@ public class ArrayUtils {
         if (CollectionUtil.isEmpty(from)) {
             return (T[]) (new Object[0]);
         }
-        return ArrayUtil.toArray(from, (Class<T>) CollectionUtil.getElementType(from.iterator()));
+        return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator()));
     }
 
     public static <T> T get(T[] array, int index) {

+ 4 - 5
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java

@@ -17,16 +17,15 @@ import java.util.regex.Pattern;
  */
 public class ValidationUtils {
 
+    private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[189]))\\d{8}$");
+
     private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
 
     private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*");
 
     public static boolean isMobile(String mobile) {
-        if (StrUtil.length(mobile) != 11) {
-            return false;
-        }
-        // TODO 芋艿,后面完善手机校验
-        return true;
+        return StringUtils.hasText(mobile)
+                && PATTERN_MOBILE.matcher(mobile).matches();
     }
 
     public static boolean isURL(String url) {

+ 4 - 4
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java

@@ -27,7 +27,7 @@ public class DictFrameworkUtils {
     /**
      * 针对 {@link #getDictDataLabel(String, String)} 的缓存
      */
-    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> getDictDataCache = CacheUtils.buildAsyncReloadingCache(
+    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
             Duration.ofMinutes(1L), // 过期时间 1 分钟
             new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
 
@@ -41,7 +41,7 @@ public class DictFrameworkUtils {
     /**
      * 针对 {@link #parseDictDataValue(String, String)} 的缓存
      */
-    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> parseDictDataCache = CacheUtils.buildAsyncReloadingCache(
+    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
             Duration.ofMinutes(1L), // 过期时间 1 分钟
             new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
 
@@ -59,12 +59,12 @@ public class DictFrameworkUtils {
 
     @SneakyThrows
     public static String getDictDataLabel(String dictType, String value) {
-        return getDictDataCache.get(new KeyValue<>(dictType, value)).getLabel();
+        return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel();
     }
 
     @SneakyThrows
     public static String parseDictDataValue(String dictType, String label) {
-        return parseDictDataCache.get(new KeyValue<>(dictType, label)).getValue();
+        return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue();
     }
 
 }

+ 8 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml

@@ -52,12 +52,18 @@
         <dependency>
             <groupId>com.alipay.sdk</groupId>
             <artifactId>alipay-sdk-java</artifactId>
-            <version>4.17.9.ALL</version>
+            <version>4.31.72.ALL</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.bouncycastle</groupId>
+                    <artifactId>bcprov-jdk15on</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>com.github.binarywang</groupId>
             <artifactId>weixin-java-pay</artifactId>
-            <version>4.1.9.B</version>
+            <version>4.3.8.B</version>
         </dependency>
         <!-- TODO 芋艿:清理 -->
 

+ 3 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl;
 
-import cn.hutool.extra.validation.ValidationUtil;
 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
@@ -10,6 +9,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
 import lombok.extern.slf4j.Slf4j;
 
+import javax.validation.Validation;
+
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
@@ -79,7 +80,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
 
     @Override
     public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        ValidationUtil.validate(reqDTO);
+        Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
         // 执行短信发送
         PayCommonResult<?> result;
         try {

+ 1 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java

@@ -53,9 +53,8 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
                 "lrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZ" +
                 "ikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB");
 
-    // TODO @tina:= 前后要有空格哈
     @InjectMocks
-    AlipayQrPayClient client=new AlipayQrPayClient(10L,config);
+    AlipayQrPayClient client = new AlipayQrPayClient(10L,config);
 
     @Mock
     private DefaultAlipayClient defaultAlipayClient;

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml

@@ -35,12 +35,12 @@
         <dependency>
             <groupId>com.github.binarywang</groupId>
             <artifactId>wx-java-mp-spring-boot-starter</artifactId>
-            <version>4.3.4.B</version>
+            <version>4.3.8.B</version>
         </dependency>
         <dependency>
             <groupId>com.github.binarywang</groupId>
             <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
-            <version>4.3.4.B</version>
+            <version>4.3.8.B</version>
         </dependency>
         <!-- TODO 芋艿:清理 -->
     </dependencies>

+ 39 - 0
yudao-framework/yudao-spring-boot-starter-captcha/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao-framework</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-spring-boot-starter-captcha</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>验证码拓展
+        1. 基于 aj-captcha 实现滑块验证码,文档:https://ajcaptcha.beliefteam.cn/captcha-doc/
+    </description>
+
+    <dependencies>
+        <!-- Spring 核心 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- DB 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-redis</artifactId>
+        </dependency>
+
+        <!-- 验证码相关 -->
+        <dependency>
+            <groupId>com.anji-plus</groupId>
+            <artifactId>spring-boot-starter-captcha</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 25 - 0
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/config/YudaoCaptchaConfiguration.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.captcha.config;
+
+import cn.hutool.core.util.ClassUtil;
+import cn.iocoder.yudao.framework.captcha.core.enums.CaptchaRedisKeyConstants;
+import cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl;
+import com.anji.captcha.service.CaptchaCacheService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+@Configuration
+public class YudaoCaptchaConfiguration {
+
+    static {
+        // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到
+        // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举
+        ClassUtil.loadClass(CaptchaRedisKeyConstants.class.getName());
+    }
+
+    @Bean
+    public CaptchaCacheService captchaCacheService(StringRedisTemplate stringRedisTemplate) {
+        return new RedisCaptchaServiceImpl(stringRedisTemplate);
+    }
+
+}

+ 28 - 0
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/enums/CaptchaRedisKeyConstants.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.framework.captcha.core.enums;
+
+import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
+import com.anji.captcha.model.vo.PointVO;
+import org.redisson.api.RLock;
+
+import java.time.Duration;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
+import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
+
+/**
+ * 验证码 Redis Key 枚举类
+ *
+ * @author 芋道源码
+ */
+public interface CaptchaRedisKeyConstants {
+
+    RedisKeyDefine AJ_CAPTCHA_REQ_LIMIT = new RedisKeyDefine("验证码的请求限流",
+            "AJ.CAPTCHA.REQ.LIMIT-%s-%s",
+            STRING, Integer.class, Duration.ofSeconds(60)); // 例如说:验证失败 5 次,get 接口锁定
+
+    RedisKeyDefine AJ_CAPTCHA_RUNNING = new RedisKeyDefine("验证码的坐标",
+            "RUNNING:CAPTCHA:%s", // AbstractCaptchaService.REDIS_CAPTCHA_KEY
+            STRING, PointVO.class, Duration.ofSeconds(120)); // {"secretKey":"PP1w2Frr2KEejD2m","x":162,"y":5}
+
+}

+ 54 - 0
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/service/RedisCaptchaServiceImpl.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.framework.captcha.core.service;
+
+import com.anji.captcha.service.CaptchaCacheService;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 基于 Redis 实现验证码的存储
+ *
+ * @author 星语
+ */
+@NoArgsConstructor // 保证 aj-captcha 的 SPI 创建
+@AllArgsConstructor
+public class RedisCaptchaServiceImpl implements CaptchaCacheService {
+
+    @Resource // 保证 aj-captcha 的 SPI 创建时的注入
+    private StringRedisTemplate stringRedisTemplate;
+
+    @Override
+    public String type() {
+        return "redis";
+    }
+
+    @Override
+    public void set(String key, String value, long expiresInSeconds) {
+        stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public boolean exists(String key) {
+        return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));
+    }
+
+    @Override
+    public void delete(String key) {
+        stringRedisTemplate.delete(key);
+    }
+
+    @Override
+    public String get(String key) {
+        return stringRedisTemplate.opsForValue().get(key);
+    }
+
+    @Override
+    public Long increment(String key, long val) {
+        return stringRedisTemplate.opsForValue().increment(key,val);
+    }
+
+}

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/package-info.java

@@ -0,0 +1,7 @@
+/**
+ * 验证码拓展
+ * 1. 基于 aj-captcha 实现滑块验证码,文档:https://ajcaptcha.beliefteam.cn/captcha-doc/
+ *
+ * @author 星语
+ */
+package cn.iocoder.yudao.framework.captcha;

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService

@@ -0,0 +1 @@
+cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  cn.iocoder.yudao.framework.captcha.config.YudaoCaptchaConfiguration

二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg1.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg2.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg3.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg4.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg5.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg6.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg7.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg8.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg9.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/1.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/10.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/11.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/12.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/13.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/14.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/15.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/16.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/17.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/18.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/19.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/8.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/9.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/2.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/3.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/4.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg1.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg10.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg2.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg3.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg4.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg5.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg6.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg7.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg8.png


二进制
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg9.png


+ 2 - 2
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java

@@ -16,11 +16,11 @@ import java.util.Set;
  */
 public class JsonLongSetTypeHandler extends AbstractJsonTypeHandler<Object> {
 
-    private static final TypeReference<Set<Long>> typeReference = new TypeReference<Set<Long>>(){};
+    private static final TypeReference<Set<Long>> TYPE_REFERENCE = new TypeReference<Set<Long>>(){};
 
     @Override
     protected Object parse(String json) {
-        return JsonUtils.parseObject(json, typeReference);
+        return JsonUtils.parseObject(json, TYPE_REFERENCE);
     }
 
     @Override

+ 4 - 4
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyRegistry.java

@@ -11,18 +11,18 @@ public class RedisKeyRegistry {
     /**
      * Redis RedisKeyDefine 数组
      */
-    private static final List<RedisKeyDefine> defines = new ArrayList<>();
+    private static final List<RedisKeyDefine> DEFINES = new ArrayList<>();
 
     public static void add(RedisKeyDefine define) {
-        defines.add(define);
+        DEFINES.add(define);
     }
 
     public static List<RedisKeyDefine> list() {
-        return defines;
+        return DEFINES;
     }
 
     public static int size() {
-        return defines.size();
+        return DEFINES.size();
     }
 
 }

+ 17 - 15
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java

@@ -81,7 +81,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
 
     /**
      * 配置 URL 的安全配置
-     *
+     * <p>
      * anyRequest          |   匹配所有请求路径
      * access              |   SpringEl表达式结果为true时可以访问
      * anonymous           |   匿名可以访问
@@ -109,8 +109,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
                 .headers().frameOptions().disable().and()
                 // 一堆自定义的 Spring Security 处理器
                 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
-                    .accessDeniedHandler(accessDeniedHandler);
-                // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高
+                .accessDeniedHandler(accessDeniedHandler);
+        // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高
 
         // 获得 @PermitAll 带来的 URL 列表,免登录
         Multimap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();
@@ -118,23 +118,25 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
         httpSecurity
                 // ①:全局共享规则
                 .authorizeRequests()
-                    // 1.1 静态资源,可匿名访问
-                    .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
-                    // 1.2 设置 @PermitAll 无需认证
-                    .antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
-                    .antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
-                    .antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
-                    .antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
-                    // 1.3 基于 yudao.security.permit-all-urls 无需认证
-                    .antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
-                    // 1.4 设置 App API 无需认证
-                    .antMatchers(buildAppApi("/**")).permitAll()
+                // 1.1 静态资源,可匿名访问
+                .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
+                // 1.2 设置 @PermitAll 无需认证
+                .antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
+                .antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
+                .antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
+                .antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
+                // 1.3 基于 yudao.security.permit-all-urls 无需认证
+                .antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
+                // 1.4 设置 App API 无需认证
+                .antMatchers(buildAppApi("/**")).permitAll()
+                // 1.5 验证码captcha 允许匿名访问
+                .antMatchers("/captcha/get", "/captcha/check").permitAll()
                 // ②:每个项目的自定义规则
                 .and().authorizeRequests(registry -> // 下面,循环设置自定义规则
                         authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
                 // ③:兜底规则,必须认证
                 .authorizeRequests()
-                    .anyRequest().authenticated()
+                .anyRequest().authenticated()
         ;
 
         // 添加 Token Filter

+ 5 - 5
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java

@@ -17,19 +17,19 @@ public class TransmittableThreadLocalSecurityContextHolderStrategy implements Se
     /**
      * 使用 TransmittableThreadLocal 作为上下文
      */
-    private static final ThreadLocal<SecurityContext> contextHolder = new TransmittableThreadLocal<>();
+    private static final ThreadLocal<SecurityContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
 
     @Override
     public void clearContext() {
-        contextHolder.remove();
+        CONTEXT_HOLDER.remove();
     }
 
     @Override
     public SecurityContext getContext() {
-        SecurityContext ctx = contextHolder.get();
+        SecurityContext ctx = CONTEXT_HOLDER.get();
         if (ctx == null) {
             ctx = createEmptyContext();
-            contextHolder.set(ctx);
+            CONTEXT_HOLDER.set(ctx);
         }
         return ctx;
     }
@@ -37,7 +37,7 @@ public class TransmittableThreadLocalSecurityContextHolderStrategy implements Se
     @Override
     public void setContext(SecurityContext context) {
         Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
-        contextHolder.set(context);
+        CONTEXT_HOLDER.set(context);
     }
 
     @Override

+ 1 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java

@@ -57,7 +57,7 @@ public class BpmMessageServiceImpl implements BpmMessageService {
         templateParams.put("taskName", reqDTO.getTaskName());
         templateParams.put("startUserNickname", reqDTO.getStartUserNickname());
         templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
-        smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
+        smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(),
                 BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams));
     }
 

+ 2 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java

@@ -154,12 +154,12 @@ public class CodegenServiceImpl implements CodegenService {
         // 构建 CodegenColumnDO 数组,只同步新增的字段
         List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
         Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, CodegenColumnDO::getColumnName);
-        // 移除已经存在的字段
-        tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()));
         // 计算需要删除的字段
         Set<String> tableFieldNames = CollectionUtils.convertSet(tableFields, TableField::getName);
         Set<Long> deleteColumnIds = codegenColumns.stream().filter(column -> !tableFieldNames.contains(column.getColumnName()))
                 .map(CodegenColumnDO::getId).collect(Collectors.toSet());
+        // 移除已经存在的字段
+        tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()));
         if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {
             throw exception(CODEGEN_SYNC_NONE_CHANGE);
         }

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java

@@ -31,7 +31,7 @@ public class CodegenBuilder {
      * 字段名与 {@link CodegenColumnListConditionEnum} 的默认映射
      * 注意,字段的匹配以后缀的方式
      */
-    private static final Map<String, CodegenColumnListConditionEnum> columnListOperationConditionMappings =
+    private static final Map<String, CodegenColumnListConditionEnum> COLUMN_LIST_OPERATION_CONDITION_MAPPINGS =
             MapUtil.<String, CodegenColumnListConditionEnum>builder()
                     .put("name", CodegenColumnListConditionEnum.LIKE)
                     .put("time", CodegenColumnListConditionEnum.BETWEEN)
@@ -42,7 +42,7 @@ public class CodegenBuilder {
      * 字段名与 {@link CodegenColumnHtmlTypeEnum} 的默认映射
      * 注意,字段的匹配以后缀的方式
      */
-    private static final Map<String, CodegenColumnHtmlTypeEnum> columnHtmlTypeMappings =
+    private static final Map<String, CodegenColumnHtmlTypeEnum> COLUMN_HTML_TYPE_MAPPINGS =
             MapUtil.<String, CodegenColumnHtmlTypeEnum>builder()
                     .put("status", CodegenColumnHtmlTypeEnum.RADIO)
                     .put("sex", CodegenColumnHtmlTypeEnum.RADIO)
@@ -143,7 +143,7 @@ public class CodegenBuilder {
         column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
                 && !column.getPrimaryKey()); // 对于主键,列表过滤不需要传递
         // 处理 listOperationCondition 字段
-        columnListOperationConditionMappings.entrySet().stream()
+        COLUMN_LIST_OPERATION_CONDITION_MAPPINGS.entrySet().stream()
                 .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))
                 .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition()));
         if (column.getListOperationCondition() == null) {
@@ -155,7 +155,7 @@ public class CodegenBuilder {
 
     private void processColumnUI(CodegenColumnDO column) {
         // 基于后缀进行匹配
-        columnHtmlTypeMappings.entrySet().stream()
+        COLUMN_HTML_TYPE_MAPPINGS.entrySet().stream()
                 .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))
                 .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType()));
         // 如果是 Boolean 类型时,设置为 radio 类型.

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm

@@ -96,7 +96,7 @@
                 @pagination="getList"/>
 
     <!-- 对话框(添加 / 修改) -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    <el-dialog :title="title" :visible.sync="open" width="500px" v-dialogDrag append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
 #foreach($column in $columns)
 #if ($column.createOperation || $column.updateOperation)

+ 6 - 6
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm

@@ -5,31 +5,31 @@ const request = useAxios()
 
 #set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
 // 查询${table.classComment}列表
-export const getPostPageApi = async (params: ${simpleClassName}PageReqVO) => {
+export const get${simpleClassName}PageApi = async (params: ${simpleClassName}PageReqVO) => {
     return await request.get({ url: '${baseURL}/page', params })
 }
 
 // 查询${table.classComment}详情
-export const getPostApi = async (id: number) => {
+export const get${simpleClassName}Api = async (id: number) => {
     return await request.get({ url: '${baseURL}/get?id=' + id })
 }
 
 // 新增${table.classComment}
-export const createPostApi = async (data: ${simpleClassName}VO) => {
+export const create${simpleClassName}Api = async (data: ${simpleClassName}VO) => {
     return await request.post({ url: '${baseURL}/create', data })
 }
 
 // 修改${table.classComment}
-export const updatePostApi = async (data: ${simpleClassName}VO) => {
+export const update${simpleClassName}Api = async (data: ${simpleClassName}VO) => {
     return await request.put({ url: '${baseURL}/update', data })
 }
 
 // 删除${table.classComment}
-export const deletePostApi = async (id: number) => {
+export const delete${simpleClassName}Api = async (id: number) => {
     return await request.delete({ url: '${baseURL}/delete?id=' + id })
 }
 
 // 导出${table.classComment} Excel
-export const exportPostApi = async (params: ${simpleClassName}ExcelReqVO) => {
+export const export${simpleClassName}Api = async (params: ${simpleClassName}ExcelReqVO) => {
     return await request.download({ url: '${baseURL}/export-excel', params })
 }

+ 3 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm

@@ -6,9 +6,9 @@
   import { useTable } from '@/hooks/web/useTable'
   import { useI18n } from '@/hooks/web/useI18n'
   import { FormExpose } from '@/components/Form'
-  import type { ${simpleClassName}VO } from '@/api/system/post/types'
-  import { rules, allSchemas } from './post.data'
-  import * as ${simpleClassName}Api from '@/api/system/post'
+  import type { ${simpleClassName}VO } from '@/api/${table.moduleName}/${simpleClassName}/types'
+  import { rules, allSchemas } from './${simpleClassName}.data'
+  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${simpleClassName}'
   const { t } = useI18n() // 国际化
 
   // ========== 列表相关 ==========

+ 1 - 1
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java

@@ -66,7 +66,7 @@ public class ProductSpuController {
 
     @GetMapping("/list")
     @ApiOperation("获得商品spu列表")
-    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = Long.class)
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
     @PreAuthorize("@ss.hasPermission('product:spu:query')")
     public CommonResult<List<SpuRespVO>> getSpuList(@RequestParam("ids") Collection<Long> ids) {
         List<ProductSpuDO> list = spuService.getSpuList(ids);

+ 1 - 2
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java

@@ -12,8 +12,7 @@ public interface ErrorCodeConstants {
     // ========== AUTH 模块 1002000000 ==========
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
-    ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
-    ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
+    ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确,原因:{}");
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
     ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1002000006, "Token 已经过期");
     ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1002000007, "手机号不存在");

+ 5 - 0
yudao-module-system/yudao-module-system-biz/pom.xml

@@ -97,6 +97,11 @@
             <artifactId>yudao-spring-boot-starter-excel</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-captcha</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 0 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java

@@ -55,7 +55,6 @@ public class AuthController {
     private PermissionService permissionService;
     @Resource
     private SocialUserService socialUserService;
-
     @Resource
     private SecurityProperties securityProperties;
 

+ 4 - 6
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java

@@ -35,13 +35,11 @@ public class AuthLoginReqVO {
 
     // ========== 图片验证码相关 ==========
 
-    @ApiModelProperty(value = "验证码", required = true, example = "1024", notes = "验证码开启时,需要传递")
+    @ApiModelProperty(value = "验证码", required = true,
+            example = "PfcH6mgr8tpXuMWFjvW6YVaqrswIuwmWI5dsVZSg7sGpWtDCUbHuDEXl3cFB1+VvCC/rAkSwK8Fad52FSuncVg==",
+            notes = "验证码开启时,需要传递")
     @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class)
-    private String code;
-
-    @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62", notes = "验证码开启时,需要传递")
-    @NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class)
-    private String uuid;
+    private String captchaVerification;
 
     // ========== 绑定社交登录时,需要传递如下参数 ==========
 

+ 0 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.http

@@ -1,3 +0,0 @@
-### 请求 /captcha/get-image 接口 => 成功
-GET {{baseUrl}}/system/captcha/get-image
-tenant-id: {{adminTenentId}}

+ 0 - 32
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.java

@@ -1,32 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.common;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
-import cn.iocoder.yudao.module.system.service.common.CaptchaService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import javax.annotation.security.PermitAll;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Api(tags = "管理后台 - 验证码")
-@RestController
-@RequestMapping("/system/captcha")
-public class CaptchaController {
-
-    @Resource
-    private CaptchaService captchaService;
-
-    @GetMapping("/get-image")
-    @PermitAll
-    @ApiOperation("生成图片验证码")
-    public CommonResult<CaptchaImageRespVO> getCaptchaImage() {
-        return success(captchaService.getCaptchaImage());
-    }
-
-}

+ 0 - 27
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.common.vo;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@ApiModel("管理后台 - 验证码图片 Response VO")
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class CaptchaImageRespVO {
-
-    @ApiModelProperty(value = "是否开启", required = true, example = "true", notes = "如果为 false,则关闭验证码功能")
-    private Boolean enable;
-
-    @ApiModelProperty(value = "uuid", example = "1b3b7d00-83a8-4638-9e37-d67011855968",
-            notes = "enable = true 时,非空!通过该 uuid 作为该验证码的标识")
-    private String uuid;
-
-    @ApiModelProperty(value = "图片", notes = "enable = true 时,非空!验证码的图片内容,使用 Base64 编码")
-    private String img;
-
-}

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java

@@ -12,8 +12,8 @@ import java.util.Set;
 @Data
 public class PermissionAssignUserRoleReqVO {
 
-    @ApiModelProperty(value = "角色编号", required = true, example = "1")
-    @NotNull(message = "角色编号不能为空")
+    @ApiModelProperty(value = "用户编号", required = true, example = "1")
+    @NotNull(message = "用户编号不能为空")
     private Long userId;
 
     @ApiModelProperty(value = "角色编号列表", example = "1,3,5")

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java

@@ -95,7 +95,7 @@ public class UserProfileController {
         return success(true);
     }
 
-    @PutMapping("/update-avatar")
+    @RequestMapping(value = "/update-avatar", method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题
     @ApiOperation("上传用户个人头像")
     public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws Exception {
         if (file.isEmpty()) {

+ 0 - 17
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/common/CaptchaConvert.java

@@ -1,17 +0,0 @@
-package cn.iocoder.yudao.module.system.convert.common;
-
-import cn.hutool.captcha.AbstractCaptcha;
-import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-@Mapper
-public interface CaptchaConvert {
-
-    CaptchaConvert INSTANCE = Mappers.getMapper(CaptchaConvert.class);
-
-    default CaptchaImageRespVO convert(String uuid, AbstractCaptcha captcha) {
-        return CaptchaImageRespVO.builder().uuid(uuid).img(captcha.getImageBase64()).build();
-    }
-
-}

+ 0 - 9
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaConfig.java

@@ -1,9 +0,0 @@
-package cn.iocoder.yudao.module.system.framework.captcha.config;
-
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-@EnableConfigurationProperties(CaptchaProperties.class)
-public class CaptchaConfig {
-}

+ 0 - 38
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaProperties.java

@@ -1,38 +0,0 @@
-package cn.iocoder.yudao.module.system.framework.captcha.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.validation.annotation.Validated;
-
-import javax.validation.constraints.NotNull;
-import java.time.Duration;
-
-@ConfigurationProperties(prefix = "yudao.captcha")
-@Validated
-@Data
-public class CaptchaProperties {
-
-    private static final Boolean ENABLE_DEFAULT = true;
-
-    /**
-     * 是否开启
-     * 注意,这里仅仅是后端 Server 是否校验,暂时不控制前端的逻辑
-     */
-    private Boolean enable = ENABLE_DEFAULT;
-    /**
-     * 验证码的过期时间
-     */
-    @NotNull(message = "验证码的过期时间不为空")
-    private Duration timeout;
-    /**
-     * 验证码的高度
-     */
-    @NotNull(message = "验证码的高度不能为空")
-    private Integer height;
-    /**
-     * 验证码的宽度
-     */
-    @NotNull(message = "验证码的宽度不能为空")
-    private Integer width;
-
-}

+ 0 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/package-info.java

@@ -1,4 +0,0 @@
-/**
- * 基于 Hutool captcha 库,实现验证码功能
- */
-package cn.iocoder.yudao.module.system.framework.captcha;

+ 32 - 33
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java

@@ -17,14 +17,17 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
-import cn.iocoder.yudao.module.system.service.common.CaptchaService;
 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
 import cn.iocoder.yudao.module.system.service.member.MemberService;
 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import com.anji.captcha.model.common.ResponseModel;
+import com.anji.captcha.model.vo.CaptchaVO;
+import com.anji.captcha.service.CaptchaService;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
@@ -47,8 +50,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
     @Resource
     private AdminUserService userService;
     @Resource
-    private CaptchaService captchaService;
-    @Resource
     private LoginLogService loginLogService;
     @Resource
     private OAuth2TokenService oauth2TokenService;
@@ -56,13 +57,19 @@ public class AdminAuthServiceImpl implements AdminAuthService {
     private SocialUserService socialUserService;
     @Resource
     private MemberService memberService;
-
     @Resource
     private Validator validator;
-
+    @Resource
+    private CaptchaService captchaService;
     @Resource
     private SmsCodeApi smsCodeApi;
 
+    /**
+     * 验证码的开关,默认为 true
+     */
+    @Value("${yudao.captcha.enable:true}")
+    private Boolean captchaEnable;
+
     @Override
     public AdminUserDO authenticate(String username, String password) {
         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
@@ -86,7 +93,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
 
     @Override
     public AuthLoginRespVO login(AuthLoginReqVO reqVO) {
-        // 判断验证码是否正确
+        // 校验验证码
         verifyCaptcha(reqVO);
 
         // 使用账号密码,进行登录
@@ -97,7 +104,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
             socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
                     reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
         }
-
         // 创建 Token 令牌,记录登录日志
         return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
     }
@@ -127,32 +133,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
         return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
     }
 
-    @VisibleForTesting
-    void verifyCaptcha(AuthLoginReqVO reqVO) {
-        // 如果验证码关闭,则不进行校验
-        if (!captchaService.isCaptchaEnable()) {
-            return;
-        }
-        // 校验验证码
-        ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);
-        // 验证码不存在
-        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
-        String code = captchaService.getCaptchaCode(reqVO.getUuid());
-        if (code == null) {
-            // 创建登录失败日志(验证码不存在)
-            createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND);
-            throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND);
-        }
-        // 验证码不正确
-        if (!code.equals(reqVO.getCode())) {
-            // 创建登录失败日志(验证码不正确)
-            createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR);
-            throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR);
-        }
-        // 正确,所以要删除下验证码
-        captchaService.deleteCaptchaCode(reqVO.getUuid());
-    }
-
     private void createLoginLog(Long userId, String username,
                                 LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
         // 插入登录日志
@@ -197,6 +177,25 @@ public class AdminAuthServiceImpl implements AdminAuthService {
         return AuthConvert.INSTANCE.convert(accessTokenDO);
     }
 
+    @VisibleForTesting
+    void verifyCaptcha(AuthLoginReqVO reqVO) {
+        // 如果验证码关闭,则不进行校验
+        if (!captchaEnable) {
+            return;
+        }
+        // 校验验证码
+        ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);
+        CaptchaVO captchaVO = new CaptchaVO();
+        captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());
+        ResponseModel response = captchaService.verification(captchaVO);
+        // 验证不通过
+        if (!response.isSuccess()) {
+            // 创建登录失败日志(验证码不正确)
+            createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR);
+            throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg());
+        }
+    }
+
     private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
         // 插入登陆日志
         createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);

+ 0 - 39
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaService.java

@@ -1,39 +0,0 @@
-package cn.iocoder.yudao.module.system.service.common;
-
-import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
-
-/**
- * 验证码 Service 接口
- */
-public interface CaptchaService {
-
-    /**
-     * 获得验证码图片
-     *
-     * @return 验证码图片
-     */
-    CaptchaImageRespVO getCaptchaImage();
-
-    /**
-     * 是否开启图片验证码
-     *
-     * @return 是否
-     */
-    Boolean isCaptchaEnable();
-
-    /**
-     * 获得 uuid 对应的验证码
-     *
-     * @param uuid 验证码编号
-     * @return 验证码
-     */
-    String getCaptchaCode(String uuid);
-
-    /**
-     * 删除 uuid 对应的验证码
-     *
-     * @param uuid 验证码编号
-     */
-    void deleteCaptchaCode(String uuid);
-
-}

+ 0 - 65
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java

@@ -1,65 +0,0 @@
-package cn.iocoder.yudao.module.system.service.common;
-
-import cn.hutool.captcha.CaptchaUtil;
-import cn.hutool.captcha.CircleCaptcha;
-import cn.hutool.core.util.IdUtil;
-import cn.iocoder.yudao.module.system.convert.common.CaptchaConvert;
-import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties;
-import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
-import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-
-/**
- * 验证码 Service 实现类
- */
-@Service
-public class CaptchaServiceImpl implements CaptchaService {
-
-    @Resource
-    private CaptchaProperties captchaProperties;
-
-    /**
-     * 验证码是否开关
-     *
-     * 虽然 {@link CaptchaProperties#getEnable()} 有该属性,但是 Apollo 在 Spring Boot 下无法刷新 @ConfigurationProperties 注解,
-     * 所以暂时只能这么处理~
-     */
-    @Value("${yudao.captcha.enable}")
-    private Boolean enable;
-
-    @Resource
-    private CaptchaRedisDAO captchaRedisDAO;
-
-    @Override
-    public CaptchaImageRespVO getCaptchaImage() {
-        if (!Boolean.TRUE.equals(enable)) {
-            return CaptchaImageRespVO.builder().enable(enable).build();
-        }
-        // 生成验证码
-        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
-        // 缓存到 Redis 中
-        String uuid = IdUtil.fastSimpleUUID();
-        captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout());
-        // 返回
-        return CaptchaConvert.INSTANCE.convert(uuid, captcha).setEnable(enable);
-    }
-
-    @Override
-    public Boolean isCaptchaEnable() {
-        return enable;
-    }
-
-    @Override
-    public String getCaptchaCode(String uuid) {
-        return captchaRedisDAO.get(uuid);
-    }
-
-    @Override
-    public void deleteCaptchaCode(String uuid) {
-        captchaRedisDAO.delete(uuid);
-    }
-
-}

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java

@@ -416,7 +416,7 @@ public class AdminUserServiceImpl implements AdminUserService {
             AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
             if (existUser == null) {
                 userMapper.insert(UserConvert.INSTANCE.convert(importUser)
-                        .setPassword(encodePassword(userInitPassword))); // 设置默认密码
+                        .setPassword(encodePassword(userInitPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组
                 respVO.getCreateUsernames().add(importUser.getUsername());
                 return;
             }

+ 77 - 83
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java

@@ -11,13 +11,12 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
-import cn.iocoder.yudao.module.system.service.common.CaptchaService;
 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
 import cn.iocoder.yudao.module.system.service.member.MemberService;
 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import org.junit.jupiter.api.BeforeEach;
+import com.anji.captcha.service.CaptchaService;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
@@ -57,11 +56,6 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
     @MockBean
     private Validator validator;
 
-    @BeforeEach
-    public void setUp() {
-        when(captchaService.isCaptchaEnable()).thenReturn(true);
-    }
-
     @Test
     public void testAuthenticate_success() {
         // 准备参数
@@ -138,82 +132,82 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
         );
     }
 
-    @Test
-    public void testCaptcha_success() {
-        // 准备参数
-        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
-
-        // mock 验证码正确
-        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
-
-        // 调用
-        authService.verifyCaptcha(reqVO);
-        // 断言
-        verify(captchaService).deleteCaptchaCode(reqVO.getUuid());
-    }
-
-    @Test
-    public void testCaptcha_notFound() {
-        // 准备参数
-        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_NOT_FOUND);
-        // 校验调用参数
-        verify(loginLogService, times(1)).createLoginLog(
-            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
-                    && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult()))
-        );
-    }
-
-    @Test
-    public void testCaptcha_codeError() {
-        // 准备参数
-        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
-
-        // mock 验证码不正确
-        String code = randomString();
-        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR);
-        // 校验调用参数
-        verify(loginLogService).createLoginLog(
-            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
-                    && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
-        );
-    }
-
-    @Test
-    public void testLogin_success() {
-        // 准备参数
-        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->
-                o.setUsername("test_username").setPassword("test_password"));
-
-        // mock 验证码正确
-        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
-        // mock user 数据
-        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username")
-                .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(userService.getUserByUsername(eq("test_username"))).thenReturn(user);
-        // mock password 匹配
-        when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true);
-        // mock 缓存登录用户到 Redis
-        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
-                .setUserType(UserTypeEnum.ADMIN.getValue()));
-        when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull()))
-                .thenReturn(accessTokenDO);
-
-        // 调用, 并断言异常
-        AuthLoginRespVO loginRespVO = authService.login(reqVO);
-        assertPojoEquals(accessTokenDO, loginRespVO);
-        // 校验调用参数
-        verify(loginLogService).createLoginLog(
-            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
-                    && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())
-                    && o.getUserId().equals(user.getId()))
-        );
-    }
+//    @Test
+//    public void testCaptcha_success() {
+//        // 准备参数
+//        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
+//
+//        // mock 验证码正确
+//        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
+//
+//        // 调用
+//        authService.verifyCaptcha(reqVO);
+//        // 断言
+//        verify(captchaService).deleteCaptchaCode(reqVO.getUuid());
+//    }
+//
+//    @Test
+//    public void testCaptcha_notFound() {
+//        // 准备参数
+//        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
+//
+//        // 调用, 并断言异常
+//        assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_NOT_FOUND);
+//        // 校验调用参数
+//        verify(loginLogService, times(1)).createLoginLog(
+//            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
+//                    && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult()))
+//        );
+//    }
+
+//    @Test
+//    public void testCaptcha_codeError() {
+//        // 准备参数
+//        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
+//
+//        // mock 验证码不正确
+//        String code = randomString();
+//        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code);
+//
+//        // 调用, 并断言异常
+//        assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR);
+//        // 校验调用参数
+//        verify(loginLogService).createLoginLog(
+//            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
+//                    && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
+//        );
+//    }
+
+//    @Test
+//    public void testLogin_success() {
+//        // 准备参数
+//        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->
+//                o.setUsername("test_username").setPassword("test_password"));
+//
+//        // mock 验证码正确
+//        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
+//        // mock user 数据
+//        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username")
+//                .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus()));
+//        when(userService.getUserByUsername(eq("test_username"))).thenReturn(user);
+//        // mock password 匹配
+//        when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true);
+//        // mock 缓存登录用户到 Redis
+//        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
+//                .setUserType(UserTypeEnum.ADMIN.getValue()));
+//        when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull()))
+//                .thenReturn(accessTokenDO);
+//
+//        // 调用, 并断言异常
+//        AuthLoginRespVO loginRespVO = authService.login(reqVO);
+//        assertPojoEquals(accessTokenDO, loginRespVO);
+//        // 校验调用参数
+//        verify(loginLogService).createLoginLog(
+//            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
+//                    && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())
+//                    && o.getUserId().equals(user.getId()))
+//        );
+//    }
 
     @Test
     public void testLogout_success() {

+ 0 - 65
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceTest.java

@@ -1,65 +0,0 @@
-package cn.iocoder.yudao.module.system.service.common;
-
-import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
-import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO;
-import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties;
-import cn.iocoder.yudao.framework.test.core.ut.BaseRedisUnitTest;
-import org.junit.jupiter.api.Test;
-import org.springframework.context.annotation.Import;
-
-import javax.annotation.Resource;
-
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
-import static org.junit.jupiter.api.Assertions.*;
-
-@Import({CaptchaServiceImpl.class, CaptchaProperties.class, CaptchaRedisDAO.class})
-public class CaptchaServiceTest extends BaseRedisUnitTest {
-
-    @Resource
-    private CaptchaServiceImpl captchaService;
-
-    @Resource
-    private CaptchaRedisDAO captchaRedisDAO;
-    @Resource
-    private CaptchaProperties captchaProperties;
-
-    @Test
-    public void testGetCaptchaImage() {
-        // 调用
-        CaptchaImageRespVO respVO = captchaService.getCaptchaImage();
-        // 断言
-        assertNotNull(respVO.getUuid());
-        assertNotNull(respVO.getImg());
-        String captchaCode = captchaRedisDAO.get(respVO.getUuid());
-        assertNotNull(captchaCode);
-    }
-
-    @Test
-    public void testGetCaptchaCode() {
-        // 准备参数
-        String uuid = randomString();
-        String code = randomString();
-        // mock 数据
-        captchaRedisDAO.set(uuid, code, captchaProperties.getTimeout());
-
-        // 调用
-        String resultCode = captchaService.getCaptchaCode(uuid);
-        // 断言
-        assertEquals(code, resultCode);
-    }
-
-    @Test
-    public void testDeleteCaptchaCode() {
-        // 准备参数
-        String uuid = randomString();
-        String code = randomString();
-        // mock 数据
-        captchaRedisDAO.set(uuid, code, captchaProperties.getTimeout());
-
-        // 调用
-        captchaService.deleteCaptchaCode(uuid);
-        // 断言
-        assertNull(captchaRedisDAO.get(uuid));
-    }
-
-}

+ 5 - 0
yudao-module-visualization/yudao-module-visualization-biz/pom.xml

@@ -68,6 +68,11 @@
             <groupId>org.jeecgframework.jimureport</groupId>
             <artifactId>jimureport-spring-boot-starter</artifactId>
         </dependency>
+        <!-- 单独依赖升级版本,解决低版本validator失败问题 -->
+        <dependency>
+            <groupId>xerces</groupId>
+            <artifactId>xercesImpl</artifactId>
+        </dependency>
 
     </dependencies>
 </project>

+ 7 - 0
yudao-server/src/main/resources/application-dev.yaml

@@ -167,6 +167,13 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta
       type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
       key-prefix: wx # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
       http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
+  miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
+    appid: wx63c280fe3248a3e7
+    secret: 6f270509224a7ae1296bbf1c8cb97aed
+    config-storage:
+      type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
+      key-prefix: wa # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
+      http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
 
 --- #################### 芋道相关配置 ####################
 

+ 12 - 12
yudao-server/src/main/resources/application-local.yaml

@@ -46,25 +46,25 @@ spring:
         master:
           name: ruoyi-vue-pro
           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 连接的示例
-#          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 连接的示例
-#          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
-#          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
-#          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
+          #          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 连接的示例
+          #          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
+          #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
+          #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
           username: root
           password: 123456
-#          username: sa
-#          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
+        #          username: sa
+        #          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
         slave: # 模拟从库,可根据自己需要修改
           name: ruoyi-vue-pro
           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 连接的示例
-#          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 连接的示例
-#          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
-#          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
-#          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
+          #          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 连接的示例
+          #          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
+          #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
+          #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
           username: root
           password: 123456
-#          username: sa
-#          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
+  #          username: sa
+  #          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:

+ 23 - 6
yudao-server/src/main/resources/application.yaml

@@ -57,6 +57,25 @@ mybatis-plus:
       logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
   type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject
 
+--- #################### 验证码相关配置 ####################
+
+aj:
+  captcha:
+    jigsaw: classpath:images/jigsaw # 滑动验证,底图路径,不配置将使用默认图片;以 classpath: 开头,取 resource 目录下路径
+    pic-click: classpath:images/pic-click # 滑动验证,底图路径,不配置将使用默认图片;以 classpath: 开头,取 resource 目录下路径
+    cache-type: redis # 缓存 local/redis...
+    cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存
+    timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行
+    type: blockPuzzle # 验证码类型 default两种都实例化。 blockPuzzle 滑块拼图 clickWord 文字点选
+    water-mark: 芋道源码 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode
+    interference-options: 2 # 滑动干扰项(0/1/2)
+    req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false
+    req-get-lock-limit: 5 # 验证失败5次,get接口锁定
+    req-get-lock-seconds: 10 # 验证失败后,锁定时间间隔
+    req-get-minute-limit: 30 # get 接口一分钟内请求数限制
+    req-check-minute-limit: 60 # check 接口一分钟内请求数限制
+    req-verify-minute-limit: 60 # verify 接口一分钟内请求数限制
+
 --- #################### 芋道相关配置 ####################
 
 yudao:
@@ -75,9 +94,7 @@ yudao:
     version: ${yudao.info.version}
     base-package: ${yudao.info.base-package}
   captcha:
-    timeout: 5m
-    width: 160
-    height: 60
+    enable: true # 验证码的开关,默认为 true;注意,优先读取数据库 infra_config 的 yudao.captcha.enable,所以请从数据库修改,可能需要重启项目
   codegen:
     base-package: ${yudao.info.base-package}
     db-schemas: ${spring.datasource.dynamic.datasource.master.name}
@@ -92,12 +109,12 @@ yudao:
     enable: true
     ignore-urls:
       - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
-      - /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关
+      - /captcha/get # 获取图片验证码,和租户无关
+      - /captcha/check # 校验图片验证码,和租户无关
       - /admin-api/infra/file/*/get/** # 获取图片,和租户无关
       - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
       - /app-api/pay/order/notify/* # 支付回调通知,不携带租户编号
-#      - /jmreport/list
-      - /jmreport/*
+      - /jmreport/* # 积木报表,无法携带租户编号
     ignore-tables:
       - system_tenant
       - system_tenant_package

+ 6 - 1
yudao-server/src/test/java/cn/iocoder/yudao/ProjectReactor.java

@@ -50,7 +50,12 @@ public class ProjectReactor {
         String projectBaseDirNew = projectBaseDir + "-new"; // 一键改名后,“新”项目所在的目录
         log.info("[main][检测新项目目录 ({})是否存在]", projectBaseDirNew);
         if (FileUtil.exist(projectBaseDirNew)) {
-            log.info("[main][新项目目录检测 ({})已存在,请更改新的目录,程序退出]", projectBaseDirNew);
+            log.error("[main][新项目目录检测 ({})已存在,请更改新的目录!程序退出]", projectBaseDirNew);
+            return;
+        }
+        // 如果新目录中存在 PACKAGE_NAME,ARTIFACT_ID 等关键字,路径会被替换,导致生成的文件不在预期目录
+        if (StrUtil.containsAny(projectBaseDirNew, PACKAGE_NAME, ARTIFACT_ID, StrUtil.upperFirst(ARTIFACT_ID))) {
+            log.error("[main][新项目目录检测 ({}) 存在冲突名称「{}」或者「{}」,请更改新的目录!程序退出]", projectBaseDirNew, PACKAGE_NAME, ARTIFACT_ID);
             return;
         }
         log.info("[main][完成新项目目录检测,新项目路径地址 ({})]", projectBaseDirNew);

+ 16 - 0
yudao-ui-admin-uniapp/.gitignore

@@ -0,0 +1,16 @@
+######################################################################
+# Build Tools
+
+/unpackage/*
+/node_modules/*
+
+######################################################################
+# Development Tools
+
+/.idea/*
+/.vscode/*
+/.hbuilderx/*
+
+package-lock.json
+yarn.lock
+

+ 34 - 0
yudao-ui-admin-uniapp/App.vue

@@ -0,0 +1,34 @@
+<script>
+  import config from './config'
+  import store from '@/store'
+  import { getAccessToken } from '@/utils/auth'
+
+  export default {
+    onLaunch: function() {
+      this.initApp()
+    },
+    methods: {
+      // 初始化应用
+      initApp() {
+        // 初始化应用配置
+        this.initConfig()
+        // 检查用户登录状态
+        //#ifdef H5
+        this.checkLogin()
+        //#endif
+      },
+      initConfig() {
+        this.globalData.config = config
+      },
+      checkLogin() {
+        if (!getAccessToken()) {
+          this.$tab.reLaunch('/pages/login')
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss">
+  @import '@/static/scss/index.scss'
+</style>

+ 21 - 0
yudao-ui-admin-uniapp/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 芋道
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 60 - 0
yudao-ui-admin-uniapp/api/login.js

@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 登录方法
+export function login(username, password, captchaVerification) {
+	const data = {
+		username,
+		password,
+		captchaVerification
+	}
+	return request({
+		url: '/system/auth/login',
+		headers: {
+			isToken: false
+		},
+		'method': 'POST',
+		'data': data
+	})
+}
+
+// 获取用户详细信息
+export function getInfo() {
+	return request({
+		url: '/system/auth/get-permission-info',
+		'method': 'GET'
+	})
+}
+
+// 退出方法
+export function logout() {
+	return request({
+		url: '/system/auth/logout',
+		'method': 'POST'
+	})
+}
+
+// 获取验证码
+export function getCaptcha(data) {
+	return request({
+		url: '/captcha/get',
+		headers: {
+			isToken: false,
+			isTenant: false
+		},
+		method: 'POST',
+		'data': data
+	})
+}
+
+// 验证验证码
+export function checkCaptcha(data) {
+	return request({
+		url: '/captcha/check',
+		headers: {
+			isToken: false,
+			isTenant: false
+		},
+		method: 'POST',
+		'data': data
+	})
+}

+ 42 - 0
yudao-ui-admin-uniapp/api/system/user.js

@@ -0,0 +1,42 @@
+import upload from '@/utils/upload'
+import request from '@/utils/request'
+
+// 用户密码重置
+export function updateUserPwd(oldPassword, newPassword) {
+  const data = {
+    oldPassword,
+    newPassword
+  }
+  return request({
+    url: '/system/user/profile/update-password',
+    method: 'PUT',
+    params: data
+  })
+}
+
+// 查询用户个人信息
+export function getUserProfile() {
+  return request({
+    url: '/system/user/profile/get',
+    method: 'GET'
+  })
+}
+
+// 修改用户个人信息
+export function updateUserProfile(data) {
+  return request({
+    url: '/system/user/profile/update',
+    method: 'PUT',
+    data: data
+  })
+}
+
+// 用户头像上传
+export function uploadAvatar(data) {
+  return upload({
+    url: '/system/user/profile/update-avatar',
+    method: 'PUT',
+    name: data.name,
+    filePath: data.filePath
+  })
+}

+ 167 - 0
yudao-ui-admin-uniapp/components/uni-section/uni-section.vue

@@ -0,0 +1,167 @@
+<template>
+	<view class="uni-section">
+		<view class="uni-section-header" @click="onClick">
+				<view class="uni-section-header__decoration" v-if="type" :class="type" />
+        <slot v-else name="decoration"></slot>
+
+        <view class="uni-section-header__content">
+          <text :style="{'font-size':titleFontSize,'color':titleColor}" class="uni-section__content-title" :class="{'distraction':!subTitle}">{{ title }}</text>
+          <text v-if="subTitle" :style="{'font-size':subTitleFontSize,'color':subTitleColor}" class="uni-section-header__content-sub">{{ subTitle }}</text>
+        </view>
+
+        <view class="uni-section-header__slot-right">
+          <slot name="right"></slot>
+        </view>
+		</view>
+
+		<view class="uni-section-content" :style="{padding: _padding}">
+			<slot />
+		</view>
+	</view>
+</template>
+
+<script>
+
+	/**
+	 * Section 标题栏
+	 * @description 标题栏
+	 * @property {String} type = [line|circle|square] 标题装饰类型
+	 * 	@value line 竖线
+	 * 	@value circle 圆形
+	 * 	@value square 正方形
+	 * @property {String} title 主标题
+	 * @property {String} titleFontSize 主标题字体大小
+	 * @property {String} titleColor 主标题字体颜色
+	 * @property {String} subTitle 副标题
+	 * @property {String} subTitleFontSize 副标题字体大小
+	 * @property {String} subTitleColor 副标题字体颜色
+	 * @property {String} padding 默认插槽 padding
+	 */
+
+	export default {
+		name: 'UniSection',
+    emits:['click'],
+		props: {
+			type: {
+				type: String,
+				default: ''
+			},
+			title: {
+				type: String,
+				required: true,
+				default: ''
+			},
+      titleFontSize: {
+        type: String,
+        default: '14px'
+      },
+			titleColor:{
+				type: String,
+				default: '#333'
+			},
+			subTitle: {
+				type: String,
+				default: ''
+			},
+      subTitleFontSize: {
+        type: String,
+        default: '12px'
+      },
+      subTitleColor: {
+        type: String,
+        default: '#999'
+      },
+			padding: {
+				type: [Boolean, String],
+				default: false
+			}
+		},
+    computed:{
+      _padding(){
+        if(typeof this.padding === 'string'){
+          return this.padding
+        }
+
+        return this.padding?'10px':''
+      }
+    },
+		watch: {
+			title(newVal) {
+				if (uni.report && newVal !== '') {
+					uni.report('title', newVal)
+				}
+			}
+		},
+    methods: {
+			onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+<style lang="scss" >
+	$uni-primary: #2979ff !default;
+
+	.uni-section {
+		background-color: #fff;
+    .uni-section-header {
+      position: relative;
+      /* #ifndef APP-NVUE */
+      display: flex;
+      /* #endif */
+      flex-direction: row;
+      align-items: center;
+      padding: 12px 10px;
+      font-weight: normal;
+
+      &__decoration{
+        margin-right: 6px;
+        background-color: $uni-primary;
+        &.line {
+          width: 4px;
+          height: 12px;
+          border-radius: 10px;
+        }
+
+        &.circle {
+          width: 8px;
+          height: 8px;
+          border-top-right-radius: 50px;
+          border-top-left-radius: 50px;
+          border-bottom-left-radius: 50px;
+          border-bottom-right-radius: 50px;
+        }
+
+        &.square {
+          width: 8px;
+          height: 8px;
+        }
+      }
+
+      &__content {
+        /* #ifndef APP-NVUE */
+        display: flex;
+        /* #endif */
+        flex-direction: column;
+        flex: 1;
+        color: #333;
+
+        .distraction {
+          flex-direction: row;
+          align-items: center;
+        }
+        &-sub {
+          margin-top: 2px;
+        }
+      }
+
+      &__slot-right{
+        font-size: 14px;
+      }
+    }
+
+    .uni-section-content{
+      font-size: 14px;
+    }
+	}
+</style>

文件差异内容过多而无法显示
+ 391 - 0
yudao-ui-admin-uniapp/components/verifition/Verify.vue


+ 14 - 0
yudao-ui-admin-uniapp/components/verifition/utils/ase.js

@@ -0,0 +1,14 @@
+import CryptoJS from 'crypto-js'
+/**
+ * @word 要加密的内容
+ * @keyWord String  服务器随机返回的关键字
+ *  */
+export function aesEncrypt(word, keyWord = "XwKsGlMcdPMEhR1B") {
+	var key = CryptoJS.enc.Utf8.parse(keyWord);
+	var srcs = CryptoJS.enc.Utf8.parse(word);
+	var encrypted = CryptoJS.AES.encrypt(srcs, key, {
+		mode: CryptoJS.mode.ECB,
+		padding: CryptoJS.pad.Pkcs7
+	});
+	return encrypted.toString();
+}

部分文件因为文件数量过多而无法显示