Explorar el Código

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

 Conflicts:
	yudao-ui-admin/yarn.lock
shizhong hace 2 años
padre
commit
3e936f3959
Se han modificado 100 ficheros con 3152 adiciones y 465 borrados
  1. 32 32
      README.md
  2. 7 0
      sql/mysql/update.sql
  3. 85 0
      sql/optional/mall/coupon.sql
  4. 274 246
      sql/optional/mall/mall.sql
  5. 76 0
      sql/optional/mall/order.sql
  6. 18 6
      yudao-dependencies/pom.xml
  7. 1 0
      yudao-framework/pom.xml
  8. 39 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java
  9. 2 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java
  10. 17 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  11. 4 5
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
  12. 211 80
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java
  13. 205 42
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java
  14. 4 4
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java
  15. 8 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml
  16. 3 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
  17. 1 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java
  18. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml
  19. 39 0
      yudao-framework/yudao-spring-boot-starter-captcha/pom.xml
  20. 25 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/config/YudaoCaptchaConfiguration.java
  21. 28 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/enums/CaptchaRedisKeyConstants.java
  22. 54 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/service/RedisCaptchaServiceImpl.java
  23. 7 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/package-info.java
  24. 1 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService
  25. 2 0
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring.factories
  26. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg1.png
  27. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg2.png
  28. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg3.png
  29. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg4.png
  30. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg5.png
  31. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg6.png
  32. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg7.png
  33. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg8.png
  34. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg9.png
  35. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/1.png
  36. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/10.png
  37. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/11.png
  38. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/12.png
  39. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/13.png
  40. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/14.png
  41. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/15.png
  42. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/16.png
  43. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/17.png
  44. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/18.png
  45. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/19.png
  46. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/8.png
  47. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/9.png
  48. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/2.png
  49. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/3.png
  50. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/4.png
  51. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg1.png
  52. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg10.png
  53. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg2.png
  54. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg3.png
  55. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg4.png
  56. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg5.png
  57. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg6.png
  58. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg7.png
  59. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg8.png
  60. BIN
      yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg9.png
  61. 2 2
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java
  62. 5 1
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
  63. 4 4
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyRegistry.java
  64. 17 15
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
  65. 5 5
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java
  66. 1 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
  67. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java
  68. 6 2
      yudao-module-mall/pom.xml
  69. 35 0
      yudao-module-mall/yudao-module-coupon-api/pom.xml
  70. 29 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponExpireTimeTypeEnum.java
  71. 29 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponFetchTypeEnum.java
  72. 29 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponForbidPreferenceEnum.java
  73. 30 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponGoodsTypeEnum.java
  74. 30 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponStatusTypeEnum.java
  75. 30 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponTypeEnum.java
  76. 29 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponUseLimitEnum.java
  77. 29 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponValidityTypeEnum.java
  78. 27 0
      yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/ErrorCodeConstants.java
  79. 62 0
      yudao-module-mall/yudao-module-coupon-biz/pom.xml
  80. 88 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/CouponController.java
  81. 110 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponBaseVO.java
  82. 3 3
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponCreateReqVO.java
  83. 90 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponExcelVO.java
  84. 91 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponExportReqVO.java
  85. 93 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponPageReqVO.java
  86. 4 4
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponRespVO.java
  87. 18 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponUpdateReqVO.java
  88. 81 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/CouponTempleteController.java
  89. 172 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteBaseVO.java
  90. 12 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteCreateReqVO.java
  91. 119 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteExcelVO.java
  92. 119 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteExportReqVO.java
  93. 122 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempletePageReqVO.java
  94. 19 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteRespVO.java
  95. 17 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteUpdateReqVO.java
  96. 38 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/convert/CouponTemplete/CouponTempleteConvert.java
  97. 34 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/convert/coupon/CouponConvert.java
  98. 158 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/dal/dataobject/CouponTemplete/CouponTempleteDO.java
  99. 118 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/dal/dataobject/coupon/CouponDO.java
  100. 98 0
      yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/dal/mysql/CouponTemplete/CouponTempleteMapper.java

+ 32 - 32
README.md

@@ -170,28 +170,28 @@ 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    | -                                                              |
+|---------------------------------------------------------------------------------------------|-----------------------|---------|----------------------------------------------------------------|
+| [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)
 
@@ -202,16 +202,16 @@ ps:核心功能已经实现,正在对接微信小程序中...
 
 ### [管理后台 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/)                                    | Vue 存储库 替代 vuex5 | 2.0.17 |
-| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化              | 9.2.0  |
-| [windicss](https://cn.windicss.org/)                                 | 下一代工具优先的 CSS 框架  | 3.5.6  |
-| [iconify](https://icon-sets.iconify.design/)                         | 在线图标库            | 2.2.1  |
+| 框架                                                                  | 说明              | 版本     |
+|----------------------------------------------------------------------|-----------------|--------|
+| [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)
 

+ 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');

+ 85 - 0
sql/optional/mall/coupon.sql

@@ -0,0 +1,85 @@
+DROP TABLE IF EXISTS `coupon`;
+CREATE TABLE `coupon`
+(
+    `id`                        bigint                                                         NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `type`                      varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci  NOT NULL COMMENT '优惠券类型 reward-满减 discount-折扣 random-随机',
+    `name`                      varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci   NOT NULL COMMENT '优惠券名称',
+    `coupon_type_id`            bigint UNSIGNED                                                         DEFAULT 0 COMMENT '优惠券类型id',
+    `coupon_code`               varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci  NOT NULL COMMENT '优惠券编码',
+    `member_id`                 bigint UNSIGNED                                                NOT NULL DEFAULT 0 COMMENT '领用人',
+    `use_order_id`              bigint UNSIGNED                                                NOT NULL DEFAULT 0 COMMENT '优惠券使用订单id',
+    `goods_type`                tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用',
+    `goods_ids`                 varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '适用商品id',
+    `at_least`                  decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '最小金额',
+    `money`                     decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '面额',
+    `discount`                  decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '1 =< 折扣 <= 9.9 当type为discount时需要添加',
+    `discount_limit`            decimal(10, 2) UNSIGNED                                        NOT NULL DEFAULT 0 COMMENT '最多折扣金额 当type为discount时可选择性添加',
+    `whether_forbid_preference` tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用',
+    `whether_expire_notice`     tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '是否开启过期提醒0-不开启 1-开启',
+    `expire_notice_fixed_term`  int(11) UNSIGNED                                               NOT NULL DEFAULT 0 COMMENT '过期前N天提醒',
+    `whether_noticed`           tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '是否已提醒',
+    `state`                     tinyint(4) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '优惠券状态 1已领用(未使用) 2已使用 3已过期',
+    `get_type`                  tinyint(1) UNSIGNED                                            NOT NULL DEFAULT 0 COMMENT '获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取',
+    `fetch_time`                datetime                                                       NOT NULL DEFAULT 0 COMMENT '领取时间',
+    `use_time`                  datetime                                                       NOT NULL DEFAULT 0 COMMENT '使用时间',
+    `start_time`                datetime                                                       NOT NULL DEFAULT 0 COMMENT '可使用的开始时间',
+    `end_time`                  datetime                                                       NOT NULL DEFAULT 0 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 = 119
+  CHARACTER SET = utf8mb4
+  COLLATE = utf8mb4_unicode_ci COMMENT = '优惠券';
+
+DROP TABLE IF EXISTS `coupon_templete`;
+CREATE TABLE `coupon_templete`
+(
+    `id`                        bigint                                                       NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `type`                      varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券类型 reward-满减 discount-折扣 random-随机',
+    `name`                      varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券名称',
+    `coupon_name_remark`        varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '名称备注',
+    `image`                     varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '优惠券图片',
+    `count`                     int(11)                                                      NOT NULL DEFAULT 0 COMMENT '发放数量',
+    `lead_count`                int(11)                                                      NOT NULL DEFAULT 0 COMMENT '已领取数量',
+    `used_count`                int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '已使用数量',
+    `goods_type`                tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 1 COMMENT '适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用',
+    `product_ids`               varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '适用商品id',
+    `has_use_limit`             tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '使用门槛0-无门槛 1-有门槛',
+    `at_least`                  decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '满多少元使用 0代表无限制',
+    `money`                     decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '发放面额 当type为reward时需要添加',
+    `discount`                  decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '1 =< 折扣 <= 9.9 当type为discount时需要添加',
+    `discount_limit`            decimal(10, 2)                                               NOT NULL DEFAULT 0 COMMENT '最多折扣金额 当type为discount时可选择性添加',
+    `min_money`                 decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '最低金额 当type为radom时需要添加',
+    `max_money`                 decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '最大金额 当type为radom时需要添加',
+    `validity_type`             tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期',
+    `start_use_time`            datetime COMMENT '使用开始日期 过期类型1时必填',
+    `end_use_time`              datetime COMMENT '使用结束日期 过期类型1时必填',
+    `fixed_term`                int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效',
+    `whether_limitless`         tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否无限制0-否 1是',
+    `max_fetch`                 int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '每人最大领取个数',
+    `whether_expire_notice`     tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否开启过期提醒0-不开启 1-开启',
+    `expire_notice_fixed_term`  int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '过期前N天提醒',
+    `whether_forbid_preference` tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用',
+    `whether_show`              int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '是否显示',
+    `discount_order_money`      decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '订单的优惠总金额',
+    `order_money`               decimal(10, 2) UNSIGNED                                      NOT NULL DEFAULT 0 COMMENT '用券总成交额',
+    `whether_forbidden`         tinyint(1) UNSIGNED                                          NOT NULL DEFAULT 0 COMMENT '是否禁止发放0-否 1-是',
+    `order_goods_num`           int(11) UNSIGNED                                             NOT NULL DEFAULT 0 COMMENT '使用优惠券购买的商品数量',
+    `status`                    tinyint(11)                                                  NOT NULL DEFAULT 0 COMMENT '状态(1进行中2已结束-1已关闭)',
+    `end_time`                  datetime 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 = 119
+  CHARACTER SET = utf8mb4
+  COLLATE = utf8mb4_unicode_ci COMMENT = '优惠券模板';

+ 274 - 246
sql/optional/mall/mall.sql

@@ -1,7 +1,7 @@
 /*
  Navicat Premium Data Transfer
 
- Source Server         : 127.0.0.1
+ Source Server         : 127.0.0.1 MySQL
  Source Server Type    : MySQL
  Source Server Version : 80026
  Source Host           : localhost:3306
@@ -11,277 +11,305 @@
  Target Server Version : 80026
  File Encoding         : 65001
 
- Date: 05/02/2022 00:50:30
+ Date: 01/08/2022 23:01:36
 */
-SET
-FOREIGN_KEY_CHECKS = 0;
+
 SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
 
 -- ----------------------------
--- Table structure for product_category
+-- Table structure for market_activity
 -- ----------------------------
-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) NOT NULL COMMENT '分类名称',
-    `icon`        varchar(100) NOT NULL DEFAULT '#' COMMENT '分类图标',
-    `banner_url`  varchar(255) NOT NULL COMMENT '分类图片',
-    `sort`        int                   DEFAULT '0' COMMENT '分类排序',
-    `description` varchar(1024)         DEFAULT NULL COMMENT '分类描述',
-    `status`      tinyint      NOT NULL COMMENT '开启状态',
-    `creator`     varchar(64)           DEFAULT '' COMMENT '创建者',
-    `create_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    `updater`     varchar(64)           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 COMMENT='商品分类';
+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
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '促销活动';
 
 -- ----------------------------
--- Table structure for product_brand
+-- Records of market_activity
 -- ----------------------------
-DROP TABLE IF EXISTS `product_brand`;
-CREATE TABLE `product_brand`
-(
-    `id`          bigint       NOT NULL AUTO_INCREMENT COMMENT '品牌编号',
-    `category_id` bigint       NOT NULL COMMENT '分类编号',
-    `name`        varchar(255) NOT NULL COMMENT '品牌名称',
-    `banner_url`  varchar(255) NOT NULL COMMENT '品牌图片',
-    `sort`        int                   DEFAULT '0' COMMENT '品牌排序',
-    `description` varchar(1024)         DEFAULT NULL COMMENT '品牌描述',
-    `status`      tinyint      NOT NULL COMMENT '状态',
-    `creator`     varchar(64)           DEFAULT '' COMMENT '创建者',
-    `create_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    `updater`     varchar(64)           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 COMMENT='品牌';
+BEGIN;
+COMMIT;
 
--- TODO 父级菜单的 id 处理: 2000 、 2001
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES (2000, '商城', '', 1, 1, 0, '/mall', 'merchant', NULL, 0);
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES (2001, '商品', '', 1, 1, 2000, 'product', 'dict', NULL, 0);
--- 商品分类 菜单 SQL
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('分类管理', '', 2, 0, 2001, 'category', '', 'mall/product/category/index', 0);
--- 按钮父菜单ID
-SELECT @parentId := LAST_INSERT_ID();
--- 按钮 SQL
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('分类查询', 'product:category:query', 3, 1, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('分类创建', 'product:category:create', 3, 2, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('分类更新', 'product:category:update', 3, 3, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('分类删除', 'product:category:delete', 3, 4, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('分类导出', 'product:category:export', 3, 5, @parentId, '', '', '', 0);
--- 品牌管理 菜单 SQL
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('品牌管理', '', 2, 1, 2001, 'brand', '', 'mall/product/brand/index', 0);
--- 按钮父菜单ID
-SELECT @parentId := LAST_INSERT_ID();
--- 按钮 SQL
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('品牌查询', 'product:brand:query', 3, 1, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('品牌创建', 'product:brand:create', 3, 2, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('品牌更新', 'product:brand:update', 3, 3, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('品牌删除', 'product:brand:delete', 3, 4, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('品牌导出', 'product:brand:export', 3, 5, @parentId, '', '', '', 0);
+-- ----------------------------
+-- Table structure for market_banner
+-- ----------------------------
+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
+) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Banner管理';
 
+-- ----------------------------
+-- Records of market_banner
+-- ----------------------------
+BEGIN;
+COMMIT;
 
 -- ----------------------------
--- Table structure for market_activity
+-- Table structure for member_address
 -- ----------------------------
-DROP TABLE IF EXISTS `market_activity`;
-CREATE TABLE `market_activity`
-(
-    `id`                    bigint      NOT NULL AUTO_INCREMENT COMMENT '活动编号',
-    `title`                 varchar(50) NOT NULL DEFAULT '' COMMENT '活动标题',
-    `activity_type`         tinyint(4) NOT NULL COMMENT '活动类型',
-    `status`                tinyint(4) NOT NULL DEFAULT '-1' COMMENT '活动状态',
-    `start_time`            datetime    NOT NULL COMMENT '开始时间',
-    `end_time`              datetime    NOT NULL COMMENT '结束时间',
-    `invalid_time`          datetime             DEFAULT NULL COMMENT '失效时间',
-    `delete_time`           datetime             DEFAULT NULL COMMENT '删除时间',
-    `time_limited_discount` varchar(2000)        DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
-    `full_privilege`        varchar(2000)        DEFAULT NULL COMMENT '限制折扣字符串,使用 JSON 序列化成字符串存储',
-    `creator`               varchar(64)          DEFAULT '' COMMENT '创建者',
-    `create_time`           datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    `updater`               varchar(64)          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 DEFAULT CHARSET=utf8mb4 COMMENT='促销活动';
+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
+) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
 
+-- ----------------------------
+-- Records of member_address
+-- ----------------------------
+BEGIN;
+INSERT INTO `member_address` (`id`, `user_id`, `name`, `mobile`, `area_id`, `post_code`, `detail_address`, `defaulted`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (21, 1, 'yunai', '15601691300', 610632, '200000', '芋道源码 233 号 666 室', b'1', '1', '2022-08-01 22:46:35', '1', '2022-08-01 22:46:35', b'0', 1);
+COMMIT;
 
--- 规格菜单 SQL
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('规格管理', '', 2, 3, 2001, 'property', '', 'mall/product/property/index', 0);
+-- ----------------------------
+-- Table structure for product_brand
+-- ----------------------------
+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
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品品牌';
 
--- 按钮父菜单ID
-SELECT @parentId := LAST_INSERT_ID();
+-- ----------------------------
+-- Records of product_brand
+-- ----------------------------
+BEGIN;
+INSERT INTO `product_brand` (`id`, `name`, `pic_url`, `sort`, `description`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '苹果', 'http://test.yudao.iocoder.cn/e3726713fa56db5717c78c011762fcc7a251db12735c3581470638b8e1fa17e2.jpeg', 0, '是上市', 0, '1', '2022-07-30 22:12:18', '1', '2022-07-30 22:13:55', b'0', 1);
+COMMIT;
 
--- 按钮 SQL
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('规格查询', 'product:property:query', 3, 1, @parentId, '', '', '', 0);
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('规格创建', 'product:property:create', 3, 2, @parentId, '', '', '', 0);
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('规格更新', 'product:property:update', 3, 3, @parentId, '', '', '', 0);
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('规格删除', 'product:property:delete', 3, 4, @parentId, '', '', '', 0);
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('规格导出', 'product:property:export', 3, 5, @parentId, '', '', '', 0);
+-- ----------------------------
+-- Table structure for product_category
+-- ----------------------------
+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
+) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品分类';
 
+-- ----------------------------
+-- Records of product_category
+-- ----------------------------
+BEGIN;
+INSERT INTO `product_category` (`id`, `parent_id`, `name`, `pic_url`, `sort`, `description`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 0, '电脑办公', 'http://test.yudao.iocoder.cn/122d548e1b3cd5dec72fe8075c6977a70f9cc13541a684ab3685f1b5df42f6bd.jpeg', 1, '1234', 0, '1', '2022-07-30 16:36:35', '1', '2022-07-30 20:27:16', b'0', 1), (2, 1, '笔记本', 'http://test.yudao.iocoder.cn/72713ac7b947600a019a18786ed0e6562e8692e253dbd35110a0a85c2469bbec.jpg', 1, '<p>测试一下</p>', 0, '1', '2022-07-30 16:38:09', '1', '2022-07-30 16:38:09', b'0', 1), (3, 1, '游戏本', 'http://test.yudao.iocoder.cn/287c50dd9f5f575f57329a0c57b2095be6d1aeba83867b905fe549f54a296feb.jpg', 2, '<p>测试一下</p>', 0, '1', '2022-07-30 16:39:09', '1', '2022-07-30 20:26:59', b'0', 1), (4, 0, '手机', 'http://test.yudao.iocoder.cn/e1b63900c78dbb661b3e383960cee5cfea7e1dd2fb22cff2e317ff025faaf8b2.jpeg', 2, '<p>123</p>', 0, '1', '2022-07-30 16:40:00', '1', '2022-07-30 16:40:09', b'0', 1), (5, 4, '5G手机', 'http://test.yudao.iocoder.cn/3af6557ac7def6423f046f5b2e920b644793420b466959aaa996a2e19068bbde.jpeg', 1, '<p><br></p>', 0, '1', '2022-07-30 16:43:00', '1', '2022-07-30 16:43:00', b'0', 1), (6, 4, '游戏手机', 'http://test.yudao.iocoder.cn/964fe9ccd1710d64ede261dc36d231918a017641986c15293c367f9f66d94d05.jpeg', 2, NULL, 0, '1', '2022-07-30 16:43:44', '1', '2022-07-30 16:43:44', b'0', 1), (7, 5, '厉害的 5G 手机', 'http://test.yudao.iocoder.cn/b287122f277838e8de368769b96217918605743bc45f3a29bda3cc7359dc66e1.png', 0, '123', 0, '1', '2022-07-30 20:38:09', '1', '2022-07-30 20:38:09', b'0', 1);
+COMMIT;
 
--- 商品菜单 SQL
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('商品管理', '', 2, 2, 2001, 'spu', '', 'mall/product/spu/index', 0);
+-- ----------------------------
+-- Table structure for product_property
+-- ----------------------------
+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 '规格名称索引'
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格名称';
 
--- 按钮父菜单ID
-SELECT @parentId := LAST_INSERT_ID();
+-- ----------------------------
+-- Records of product_property
+-- ----------------------------
+BEGIN;
+COMMIT;
 
--- 按钮 SQL
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('商品查询', 'product:spu:query', 3, 1, @parentId, '', '', '', 0);
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('商品创建', 'product:spu:create', 3, 2, @parentId, '', '', '', 0);
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('商品更新', 'product:spu:update', 3, 3, @parentId, '', '', '', 0);
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('商品删除', 'product:spu:delete', 3, 4, @parentId, '', '', '', 0);
-INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('商品导出', 'product:spu:export', 3, 5, @parentId, '', '', '', 0);
+-- ----------------------------
+-- Table structure for product_property_value
+-- ----------------------------
+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
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格值';
 
+-- ----------------------------
+-- Records of product_property_value
+-- ----------------------------
+BEGIN;
+COMMIT;
 
--- 规格名称表
-drop table if exists product_property;
-create table product_property
-(
-    id          bigint NOT NULL AUTO_INCREMENT comment '主键',
-    name        varchar(64) comment '规格名称',
-    status      tinyint comment '状态: 0 开启 ,1 禁用',
-    create_time datetime        default current_timestamp comment '创建时间',
-    update_time datetime        default current_timestamp on update current_timestamp comment '更新时间',
-    creator     varchar(64) comment '创建人',
-    updater     varchar(64) comment '更新人',
-    tenant_id   bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
-    deleted   bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-    primary key (id),
-    key         idx_name ( name (32)) comment '规格名称索引'
-) comment '规格名称' character set utf8mb4
-                 collate utf8mb4_general_ci;
+-- ----------------------------
+-- Table structure for product_sku
+-- ----------------------------
+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 '更新人',
+`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB COMMENT='商品sku';
 
--- 规格值表
-drop table if exists product_property_value;
-create table product_property_value
-(
-    id          bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
-    property_id bigint comment '规格键id',
-    name        varchar(128) comment '规格值名字',
-    status      tinyint comment '状态: 1 开启 ,2 禁用',
-    create_time datetime        default current_timestamp comment '创建时间',
-    update_time datetime        default current_timestamp on update current_timestamp comment '更新时间',
-    creator     varchar(64) comment '创建人',
-    updater     varchar(64) comment '更新人',
-    tenant_id   bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
-    deleted   bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-    primary key (id)
-) comment '规格值' character set utf8mb4
-                collate utf8mb4_general_ci;
+-- ----------------------------
+-- Records of product_sku
+-- ----------------------------
+BEGIN;
+COMMIT;
 
--- spu
-drop table if exists product_spu;
-create table product_spu
-(
-    id          bigint        NOT NULL AUTO_INCREMENT COMMENT '主键',
-    name        varchar(128) comment '商品名称',
-    sell_point  varchar(128)  not null comment '卖点',
-    description text          not null comment '描述',
-    category_id bigint        not null comment '分类id',
-    pic_urls    varchar(1024) not null default '' comment '商品主图地址\n     *\n     * 数组,以逗号分隔\n 最多上传15张',
-    sort        int           not null default 0 comment '排序字段',
-    like_count  int comment '点赞初始人数',
-    price       int comment '价格 单位使用:分',
-    quantity    int comment '库存数量',
-    status      bit(1) comment '上下架状态: 0 上架(开启) 1 下架(禁用)',
-    create_time datetime               default current_timestamp comment '创建时间',
-    update_time datetime               default current_timestamp on update current_timestamp comment '更新时间',
-    creator     varchar(64) comment '创建人',
-    updater     varchar(64) comment '更新人',
-    tenant_id   bigint        NOT NULL DEFAULT '0' COMMENT '租户编号',
-    deleted   bit(1)        NOT NULL DEFAULT b'0' COMMENT '是否删除',
-    primary key (id)
-) comment '商品spu' character set utf8mb4
-                  collate utf8mb4_general_ci;
+-- ----------------------------
+-- Table structure for product_spu
+-- ----------------------------
+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
+) ENGINE=InnoDB COMMENT='商品spu';
 
+-- ----------------------------
+-- Records of product_spu
+-- ----------------------------
+BEGIN;
+COMMIT;
 
--- sku
-drop table if exists product_sku;
-create table product_sku
-(
-    id             bigint       NOT NULL AUTO_INCREMENT COMMENT '主键',
-    spu_id         bigint       not null comment 'spu编号',
-    properties     varchar(64)  not null comment '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
-    price          int          not null DEFAULT -1 comment '销售价格,单位:分',
-    original_price int          not null DEFAULT -1 comment '原价, 单位: 分',
-    cost_price     int          not null DEFAULT -1 comment '成本价,单位: 分',
-    bar_code       varchar(64)  not null comment '条形码',
-    pic_url        VARCHAR(128) not null comment '图片地址',
-    status         tinyint comment '状态: 0-正常 1-禁用',
-    create_time    datetime              default current_timestamp comment '创建时间',
-    update_time    datetime              default current_timestamp on update current_timestamp comment '更新时间',
-    creator        varchar(64) comment '创建人',
-    updater        varchar(64) comment '更新人',
-    tenant_id      bigint       NOT NULL DEFAULT '0' COMMENT '租户编号',
-    deleted      bit(1)       NOT NULL DEFAULT b'0' COMMENT '是否删除',
-    primary key (id)
-) comment '商品sku' character set utf8mb4
-                  collate utf8mb4_general_ci;
+SET FOREIGN_KEY_CHECKS = 1;
 
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2000, '商品中心', '', 1, 60, 0, '/product', 'merchant', NULL, 0, b'1', b'1', '', '2022-07-29 15:53:53', '1', '2022-07-30 22:26:19', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2002, '商品分类', '', 2, 2, 2000, 'category', 'dict', 'mall/product/category/index', 0, b'1', b'1', '', '2022-07-29 15:53:53', '1', '2022-07-30 22:23:37', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2003, '分类查询', 'product:category:query', 3, 1, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2004, '分类创建', 'product:category:create', 3, 2, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2005, '分类更新', 'product:category:update', 3, 3, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2006, '分类删除', 'product:category:delete', 3, 4, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2007, '分类导出', 'product:category:export', 3, 5, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-30 13:52:13', b'1');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2008, '商品品牌', '', 2, 1, 2000, 'brand', 'dashboard', 'mall/product/brand/index', 0, b'1', b'1', '', '2022-07-30 13:52:44', '1', '2022-07-30 22:23:43', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2009, '品牌查询', 'product:brand:query', 3, 1, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2010, '品牌创建', 'product:brand:create', 3, 2, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2011, '品牌更新', 'product:brand:update', 3, 3, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2012, '品牌删除', 'product:brand:delete', 3, 4, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2013, '品牌导出', 'product:brand:export', 3, 5, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 14:15:00', b'1');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2014, '商品管理', '', 2, 0, 2000, 'spu', 'link', 'mall/product/spu/index', 0, b'1', b'1', '', '2022-07-30 14:22:58', '1', '2022-07-30 22:26:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2015, '商品查询', 'product:spu:query', 3, 1, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2016, '商品创建', 'product:spu:create', 3, 2, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2017, '商品更新', 'product:spu:update', 3, 3, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2018, '商品删除', 'product:spu:delete', 3, 4, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2019, '规格管理', '', 2, 3, 2000, 'property', '', 'mall/product/property/index', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2020, '规格查询', 'product:property:query', 3, 1, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2021, '规格创建', 'product:property:create', 3, 2, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2022, '规格更新', 'product:property:update', 3, 3, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2023, '规格删除', 'product:property:delete', 3, 4, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2024, '规格导出', 'product:property:export', 3, 5, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2025, 'Banner管理', '', 2, 1, 2000, 'brand', '', 'mall/market/banner/index', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2026, 'Banner查询', 'market:banner:query', 3, 1, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2027, 'Banner创建', 'market:banner:create', 3, 2, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2028, 'Banner更新', 'market:banner:update', 3, 3, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
+INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2029, 'Banner删除', 'market:banner:delete', 3, 4, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
 
--- Market-Banner管理SQL
-drop table if exists market_banner;
-CREATE TABLE `market_banner` (
-  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Banner编号',
-  `title` varchar(64) NOT NULL DEFAULT '' COMMENT 'Banner标题',
-  `pic_url` varchar(255) NOT NULL COMMENT '图片URL',
-  `status` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '活动状态',
-  `url` varchar(255) NOT NULL COMMENT '跳转地址',
-  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
-  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `updater` varchar(64) 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(20) NOT NULL DEFAULT '0' COMMENT '租户编号',
-  `sort` tinyint(4) DEFAULT NULL COMMENT '排序',
-  `memo` varchar(255) DEFAULT NULL COMMENT '描述',
-  PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='Banner管理';
--- 菜单 SQL
-INSERT INTO `system_menu`(`id`,`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES (2026, 'Banner管理', '', 2, 1, 2000, 'brand', '', 'mall/market/banner/index', 0);
--- 按钮父菜单ID
-SELECT @parentId := LAST_INSERT_ID();
--- 按钮 SQL
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('Banner查询', 'market:banner:query', 3, 1, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('Banner创建', 'market:banner:create', 3, 2, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('Banner更新', 'market:banner:update', 3, 3, @parentId, '', '', '', 0);
-INSERT INTO `system_menu`(`name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`)
-VALUES ('Banner删除', 'market:banner:delete', 3, 4, @parentId, '', '', '', 0);

+ 76 - 0
sql/optional/mall/order.sql

@@ -0,0 +1,76 @@
+/**todo cancelType 设置默认值 0?*/
+CREATE TABLE `trade_order`
+(
+    `id`                      bigint          NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `sn`                      varchar(32)     NOT NULL COMMENT '订单流水号',
+    `type`                    int             NOT NULL DEFAULT '0' COMMENT '订单类型:[0:普通订单 1:秒杀订单 2:拼团订单 3:砍价订单]',
+    `terminal`                int             NOT NULL COMMENT '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]',
+    `user_id`                 bigint unsigned NOT NULL COMMENT '用户编号',
+    `user_ip`                 varchar(30)     NOT NULL DEFAULT '' COMMENT '用户 IP',
+    `user_remark`             varchar(200)             DEFAULT NULL COMMENT '用户备注',
+    `status`                  int             NOT NULL DEFAULT '0' COMMENT '订单状态:[0:待付款 1:待发货 2:待收货 3:已完成 4:已关闭]',
+    `product_count`           int             NOT NULL COMMENT '购买的商品数量',
+    `cancel_type`             int             NOT NULL COMMENT '取消类型:[10:超时未支付 20:退款关闭 30:买家取消 40:已通过货到付款交易]',
+    `remark`                  varchar(200)             DEFAULT NULL COMMENT '商家备注',
+    `payed`                   bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否已支付:[0:未支付 1:已经支付过]',
+    `finish_time`             datetime                 DEFAULT NULL COMMENT '订单完成时间',
+    `cancel_time`             datetime                 DEFAULT NULL COMMENT '订单取消时间',
+    `sku_original_price`      int             NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
+    `sku_promotion_price`     int             NOT NULL DEFAULT '0' COMMENT '商品优惠(总),单位:分',
+    `order_promotion_price`   int             NOT NULL DEFAULT '0' COMMENT '订单优惠(总),单位:分',
+    `delivery_price`          int             NOT NULL DEFAULT '0' COMMENT '运费金额,单位:分',
+    `pay_price`               int             NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
+    `pay_order_id`            int             NOT NULL COMMENT '支付订单编号',
+    `pay_channel`             int             NOT NULL COMMENT '支付成功的支付渠道',
+    `delivery_type`           int             NOT NULL DEFAULT '1' COMMENT '配送方式:[1:快递发货 2:自提]',
+    `actual_delivery_type`    int             NOT NULL DEFAULT '1' COMMENT '实际的配送方式:[1:快递发货 2:自提]',
+    `delivery_templateid`     int                      DEFAULT NULL COMMENT '配置模板的编号',
+    `express_no`              int                      DEFAULT NULL COMMENT '物流公司单号',
+    `delivery_status`         bit(1)          NOT NULL DEFAULT b'0' COMMENT '发货状态[0:未发货 1:已发货]',
+    `delivery_time`           datetime                 DEFAULT NULL COMMENT '发货时间',
+    `receive_time`            datetime                 DEFAULT NULL COMMENT '收货时间',
+    `receiver_name`           varchar(20)     NOT NULL COMMENT '收件人名称',
+    `receiver_mobile`         varchar(20)     NOT NULL COMMENT '收件人手机',
+    `receiver_area_id`        int             NOT NULL COMMENT '收件人地区编号',
+    `receiver_post_code`      int                      DEFAULT NULL COMMENT '收件人邮编',
+    `receiver_detail_address` varchar(255)    NOT NULL COMMENT '收件人详细地址',
+    `refund_status`           int             NOT NULL DEFAULT '0' COMMENT '订单状态:[0:未退款 1:部分退款 2:全部退款]',
+    `refund_price`            int             NOT NULL DEFAULT '0' COMMENT '退款金额,单位:分',
+    `coupon_id`               bigint unsigned NOT NULL COMMENT '优惠劵编号',
+    `creator`                 varchar(64)              DEFAULT '' COMMENT '创建者',
+    `create_time`             datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`                 varchar(64)              DEFAULT '' COMMENT '更新者',
+    `update_time`             datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`                 bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='交易订单表';
+
+
+DROP TABLE IF EXISTS `trade_order_item`;
+CREATE TABLE `trade_order_item`
+(
+    `id`                    bigint          NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `user_id`               bigint unsigned NOT NULL COMMENT '用户编号',
+    `order_Id`              bigint unsigned NOT NULL COMMENT '订单编号',
+    `spu_id`                bigint unsigned NOT NULL COMMENT '商品 SPU 编号',
+    `sku_id`                bigint unsigned NOT NULL COMMENT '商品 SKU 编号',
+    `properties`            json                     DEFAULT NULL COMMENT '规格值数组,JSON 格式',
+    `name`                  varchar(128)    NOT NULL DEFAULT '' COMMENT '商品名称',
+    `pic_url`               varchar(200)             DEFAULT NULL COMMENT '商品图片',
+    `count`                 int             NOT NULL COMMENT '购买数量',
+    `commented`             bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否评论:[0:未评论 1:已评论]',
+    `original_price`        int             NOT NULL DEFAULT '0' COMMENT '商品原价(单),单位:分',
+    `total_original_price`  int             NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
+    `total_promotion_price` int             NOT NULL DEFAULT '0' COMMENT '商品级优惠(总),单位:分',
+    `present_price`         int             NOT NULL DEFAULT '0' COMMENT '最终购买金额(单),单位:分。',
+    `total_present_price`   int             NOT NULL DEFAULT '0' COMMENT '最终购买金额(总),单位:分。',
+    `total_pay_price`       int             NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
+    `refund_status`         int             NOT NULL DEFAULT '0' COMMENT '退款状态:[0:未申请退款 1:申请退款 2:等待退款 3:退款成功]',
+    `refund_total`          int             NOT NULL DEFAULT '0' COMMENT '退款总金额,单位:分',
+    `creator`               varchar(64)              DEFAULT '' COMMENT '创建者',
+    `create_time`           datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`               varchar(64)              DEFAULT '' COMMENT '更新者',
+    `update_time`           datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`               bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='交易订单明细表';

+ 18 - 6
yudao-dependencies/pom.xml

@@ -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,11 +57,12 @@
         <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>
@@ -130,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>
@@ -452,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>

+ 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>

+ 39 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.framework.common.enums;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 终端的枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum TerminalEnum implements IntArrayValuable {
+
+    //TODO terminal 重复,请参考 '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]'
+    MINI_PROGRAM(1, "小程序"),
+    H5(2, "H5"),
+    IOS(3, "iOS"),
+    ANDROID(3, "安卓"),;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();
+
+    /**
+     * 终端
+     */
+    private final Integer terminal;
+    /**
+     * 终端名
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+}

+ 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) {

+ 17 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -173,6 +173,23 @@ public class CollectionUtils {
         return valueFunc.apply(t);
     }
 
+    public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
+        if (CollUtil.isEmpty(from)) {
+            return null;
+        }
+        assert from.size() > 0; // 断言,避免告警
+        T t = from.stream().min(Comparator.comparing(valueFunc)).get();
+        return valueFunc.apply(t);
+    }
+
+    public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc, BinaryOperator<V> accumulator) {
+        if (CollUtil.isEmpty(from)) {
+            return null;
+        }
+        assert from.size() > 0; // 断言,避免告警
+        return from.stream().map(valueFunc).reduce(accumulator).get();
+    }
+
     public static <T> void addIfNotNull(Collection<T> coll, T item) {
         if (item == null) {
             return;

+ 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) {

+ 211 - 80
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java

@@ -18,7 +18,6 @@ import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
 import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
 import net.sf.jsqlparser.expression.operators.relational.InExpression;
-import net.sf.jsqlparser.expression.operators.relational.ItemsList;
 import net.sf.jsqlparser.schema.Table;
 import net.sf.jsqlparser.statement.delete.Delete;
 import net.sf.jsqlparser.statement.select.*;
@@ -37,7 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * 数据权限拦截器,通过 {@link DataPermissionRule} 数据权限规则,重写 SQL 的方式来实现
- * 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, Table)} 方法
+ * 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, List)} 方法
  *
  * 整体的代码实现上,参考 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor} 实现。
  * 所以每次 MyBatis Plus 升级时,需要 Review 下其具体的实现是否有变更!
@@ -53,8 +52,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
     private final MappedStatementCache mappedStatementCache = new MappedStatementCache();
 
     @Override // SELECT 场景
-    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
-                            RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
+    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
         // 获得 Mapper 对应的数据权限的规则
         List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId());
         if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过
@@ -68,12 +66,14 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
             // 处理 SQL
             mpBs.sql(parserSingle(mpBs.sql(), null));
         } finally {
+            // 添加是否需要重写的缓存
             addMappedStatementCache(ms);
+            // 清空上下文
             ContextHolder.clear();
         }
     }
 
-    @Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景
+    @Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景(因为 INSERT 不需要数据权限)
     public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
         PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
         MappedStatement ms = mpSh.mappedStatement();
@@ -92,7 +92,9 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
                 // 处理 SQL
                 mpBs.sql(parserMulti(mpBs.sql(), null));
             } finally {
+                // 添加是否需要重写的缓存
                 addMappedStatementCache(ms);
+                // 清空上下文
                 ContextHolder.clear();
             }
         }
@@ -107,24 +109,6 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
         }
     }
 
-    protected void processSelectBody(SelectBody selectBody) {
-        if (selectBody == null) {
-            return;
-        }
-        if (selectBody instanceof PlainSelect) {
-            processPlainSelect((PlainSelect) selectBody);
-        } else if (selectBody instanceof WithItem) {
-            WithItem withItem = (WithItem) selectBody;
-            processSelectBody(withItem.getSubSelect().getSelectBody());
-        } else {
-            SetOperationList operationList = (SetOperationList) selectBody;
-            List<SelectBody> selectBodys = operationList.getSelects();
-            if (CollectionUtils.isNotEmpty(selectBodys)) {
-                selectBodys.forEach(this::processSelectBody);
-            }
-        }
-    }
-
     /**
      * update 语句处理
      */
@@ -142,28 +126,77 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
         delete.setWhere(this.builderExpression(delete.getWhere(), delete.getTable()));
     }
 
+    // ========== 和 TenantLineInnerInterceptor 一致的逻辑 ==========
+
+    protected void processSelectBody(SelectBody selectBody) {
+        if (selectBody == null) {
+            return;
+        }
+        if (selectBody instanceof PlainSelect) {
+            processPlainSelect((PlainSelect) selectBody);
+        } else if (selectBody instanceof WithItem) {
+            WithItem withItem = (WithItem) selectBody;
+            processSelectBody(withItem.getSubSelect().getSelectBody());
+        } else {
+            SetOperationList operationList = (SetOperationList) selectBody;
+            List<SelectBody> selectBodyList = operationList.getSelects();
+            if (CollectionUtils.isNotEmpty(selectBodyList)) {
+                selectBodyList.forEach(this::processSelectBody);
+            }
+        }
+    }
+
     /**
      * 处理 PlainSelect
      */
     protected void processPlainSelect(PlainSelect plainSelect) {
-        FromItem fromItem = plainSelect.getFromItem();
-        Expression where = plainSelect.getWhere();
-        processWhereSubSelect(where);
-        if (fromItem instanceof Table) {
-            Table fromTable = (Table) fromItem;
-            plainSelect.setWhere(builderExpression(where, fromTable));
-        } else {
-            processFromItem(fromItem);
-        }
         //#3087 github
         List<SelectItem> selectItems = plainSelect.getSelectItems();
         if (CollectionUtils.isNotEmpty(selectItems)) {
             selectItems.forEach(this::processSelectItem);
         }
+
+        // 处理 where 中的子查询
+        Expression where = plainSelect.getWhere();
+        processWhereSubSelect(where);
+
+        // 处理 fromItem
+        FromItem fromItem = plainSelect.getFromItem();
+        List<Table> list = processFromItem(fromItem);
+        List<Table> mainTables = new ArrayList<>(list);
+
+        // 处理 join
         List<Join> joins = plainSelect.getJoins();
         if (CollectionUtils.isNotEmpty(joins)) {
-            processJoins(joins);
+            mainTables = processJoins(mainTables, joins);
+        }
+
+        // 当有 mainTable 时,进行 where 条件追加
+        if (CollectionUtils.isNotEmpty(mainTables)) {
+            plainSelect.setWhere(builderExpression(where, mainTables));
+        }
+    }
+
+    private List<Table> processFromItem(FromItem fromItem) {
+        // 处理括号括起来的表达式
+        while (fromItem instanceof ParenthesisFromItem) {
+            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
+        }
+
+        List<Table> mainTables = new ArrayList<>();
+        // 无 join 时的处理逻辑
+        if (fromItem instanceof Table) {
+            Table fromTable = (Table) fromItem;
+            mainTables.add(fromTable);
+        } else if (fromItem instanceof SubJoin) {
+            // SubJoin 类型则还需要添加上 where 条件
+            List<Table> tables = processSubJoin((SubJoin) fromItem);
+            mainTables.addAll(tables);
+        } else {
+            // 处理下 fromItem
+            processOtherFromItem(fromItem);
         }
+        return mainTables;
     }
 
     /**
@@ -191,7 +224,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
             return;
         }
         if (where instanceof FromItem) {
-            processFromItem((FromItem) where);
+            processOtherFromItem((FromItem) where);
             return;
         }
         if (where.toString().indexOf("SELECT") > 0) {
@@ -204,9 +237,9 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
             } else if (where instanceof InExpression) {
                 // in
                 InExpression expression = (InExpression) where;
-                ItemsList itemsList = expression.getRightItemsList();
-                if (itemsList instanceof SubSelect) {
-                    processSelectBody(((SubSelect) itemsList).getSelectBody());
+                Expression inExpression = expression.getRightExpression();
+                if (inExpression instanceof SubSelect) {
+                    processSelectBody(((SubSelect) inExpression).getSelectBody());
                 }
             } else if (where instanceof ExistsExpression) {
                 // exists
@@ -239,7 +272,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
      * <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
      * <p> fixed gitee pulls/141</p>
      *
-     * @param function 函数
+     * @param function
      */
     protected void processFunction(Function function) {
         ExpressionList parameters = function.getParameters();
@@ -257,22 +290,19 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
     /**
      * 处理子查询等
      */
-    protected void processFromItem(FromItem fromItem) {
-        if (fromItem instanceof SubJoin) {
-            SubJoin subJoin = (SubJoin) fromItem;
-            if (subJoin.getJoinList() != null) {
-                processJoins(subJoin.getJoinList());
-            }
-            if (subJoin.getLeft() != null) {
-                processFromItem(subJoin.getLeft());
-            }
-        } else if (fromItem instanceof SubSelect) {
+    protected void processOtherFromItem(FromItem fromItem) {
+        // 去除括号
+        while (fromItem instanceof ParenthesisFromItem) {
+            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
+        }
+
+        if (fromItem instanceof SubSelect) {
             SubSelect subSelect = (SubSelect) fromItem;
             if (subSelect.getSelectBody() != null) {
                 processSelectBody(subSelect.getSelectBody());
             }
         } else if (fromItem instanceof ValuesList) {
-            logger.debug("Perform a subquery, if you do not give us feedback");
+            logger.debug("Perform a subQuery, if you do not give us feedback");
         } else if (fromItem instanceof LateralSubSelect) {
             LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
             if (lateralSubSelect.getSubSelect() != null) {
@@ -284,75 +314,176 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
         }
     }
 
+    /**
+     * 处理 sub join
+     *
+     * @param subJoin subJoin
+     * @return Table subJoin 中的主表
+     */
+    private List<Table> processSubJoin(SubJoin subJoin) {
+        List<Table> mainTables = new ArrayList<>();
+        if (subJoin.getJoinList() != null) {
+            List<Table> list = processFromItem(subJoin.getLeft());
+            mainTables.addAll(list);
+            mainTables = processJoins(mainTables, subJoin.getJoinList());
+        }
+        return mainTables;
+    }
+
     /**
      * 处理 joins
      *
-     * @param joins join 集合
+     * @param mainTables 可以为 null
+     * @param joins      join 集合
+     * @return List<Table> 右连接查询的 Table 列表
      */
-    private void processJoins(List<Join> joins) {
+    private List<Table> processJoins(List<Table> mainTables, List<Join> joins) {
+        // join 表达式中最终的主表
+        Table mainTable = null;
+        // 当前 join 的左表
+        Table leftTable = null;
+
+        if (mainTables == null) {
+            mainTables = new ArrayList<>();
+        } else if (mainTables.size() == 1) {
+            mainTable = mainTables.get(0);
+            leftTable = mainTable;
+        }
+
         //对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
-        Deque<Table> tables = new LinkedList<>();
+        Deque<List<Table>> onTableDeque = new LinkedList<>();
         for (Join join : joins) {
             // 处理 on 表达式
-            FromItem fromItem = join.getRightItem();
-            if (fromItem instanceof Table) {
-                Table fromTable = (Table) fromItem;
+            FromItem joinItem = join.getRightItem();
+
+            // 获取当前 join 的表,subJoint 可以看作是一张表
+            List<Table> joinTables = null;
+            if (joinItem instanceof Table) {
+                joinTables = new ArrayList<>();
+                joinTables.add((Table) joinItem);
+            } else if (joinItem instanceof SubJoin) {
+                joinTables = processSubJoin((SubJoin) joinItem);
+            }
+
+            if (joinTables != null) {
+
+                // 如果是隐式内连接
+                if (join.isSimple()) {
+                    mainTables.addAll(joinTables);
+                    continue;
+                }
+
+                // 当前表是否忽略
+                Table joinTable = joinTables.get(0);
+
+                List<Table> onTables = null;
+                // 如果不要忽略,且是右连接,则记录下当前表
+                if (join.isRight()) {
+                    mainTable = joinTable;
+                    if (leftTable != null) {
+                        onTables = Collections.singletonList(leftTable);
+                    }
+                } else if (join.isLeft()) {
+                    onTables = Collections.singletonList(joinTable);
+                } else if (join.isInner()) {
+                    if (mainTable == null) {
+                        onTables = Collections.singletonList(joinTable);
+                    } else {
+                        onTables = Arrays.asList(mainTable, joinTable);
+                    }
+                    mainTable = null;
+                }
+
+                mainTables = new ArrayList<>();
+                if (mainTable != null) {
+                    mainTables.add(mainTable);
+                }
+
                 // 获取 join 尾缀的 on 表达式列表
                 Collection<Expression> originOnExpressions = join.getOnExpressions();
                 // 正常 join on 表达式只有一个,立刻处理
-                if (originOnExpressions.size() == 1) {
-                    processJoin(join);
+                if (originOnExpressions.size() == 1 && onTables != null) {
+                    List<Expression> onExpressions = new LinkedList<>();
+                    onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables));
+                    join.setOnExpressions(onExpressions);
+                    leftTable = joinTable;
                     continue;
                 }
-                tables.push(fromTable);
+                // 表名压栈,忽略的表压入 null,以便后续不处理
+                onTableDeque.push(onTables);
                 // 尾缀多个 on 表达式的时候统一处理
                 if (originOnExpressions.size() > 1) {
                     Collection<Expression> onExpressions = new LinkedList<>();
                     for (Expression originOnExpression : originOnExpressions) {
-                        Table currentTable = tables.poll();
-                        onExpressions.add(builderExpression(originOnExpression, currentTable));
+                        List<Table> currentTableList = onTableDeque.poll();
+                        if (CollectionUtils.isEmpty(currentTableList)) {
+                            onExpressions.add(originOnExpression);
+                        } else {
+                            onExpressions.add(builderExpression(originOnExpression, currentTableList));
+                        }
                     }
                     join.setOnExpressions(onExpressions);
                 }
+                leftTable = joinTable;
             } else {
-                // 处理右边连接的子表达式
-                processFromItem(fromItem);
+                processOtherFromItem(joinItem);
+                leftTable = null;
             }
         }
+
+        return mainTables;
     }
 
+    // ========== 和 TenantLineInnerInterceptor 存在差异的逻辑:关键,实现权限条件的拼接 ==========
+
     /**
-     * 处理联接语句
+     * 处理条件
+     *
+     * @param currentExpression 当前 where 条件
+     * @param table             单个表
      */
-    protected void processJoin(Join join) {
-        if (join.getRightItem() instanceof Table) {
-            Table fromTable = (Table) join.getRightItem();
-            Expression originOnExpression = CollUtil.getFirst(join.getOnExpressions());
-            originOnExpression = builderExpression(originOnExpression, fromTable);
-            join.setOnExpressions(CollUtil.newArrayList(originOnExpression));
-        }
+    protected Expression builderExpression(Expression currentExpression, Table table) {
+        return this.builderExpression(currentExpression, Collections.singletonList(table));
     }
 
     /**
      * 处理条件
+     *
+     * @param currentExpression 当前 where 条件
+     * @param tables 多个表
      */
-    protected Expression builderExpression(Expression currentExpression, Table table) {
-        // 获得 Table 对应的数据权限条件
-        Expression equalsTo = buildDataPermissionExpression(table);
-        if (equalsTo == null) { // 如果没条件,则返回 currentExpression 默认
+    protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
+        // 没有表需要处理直接返回
+        if (CollectionUtils.isEmpty(tables)) {
             return currentExpression;
         }
 
-        // 表达式为空,则直接返回 equalsTo
+        // 第一步,获得 Table 对应的数据权限条件
+        Expression dataPermissionExpression = null;
+        for (Table table : tables) {
+            // 构建每个表的权限 Expression 条件
+            Expression expression = buildDataPermissionExpression(table);
+            if (expression == null) {
+                continue;
+            }
+            // 合并到 dataPermissionExpression 中
+            dataPermissionExpression = dataPermissionExpression == null ? expression
+                    : new AndExpression(dataPermissionExpression, expression);
+        }
+
+        // 第二步,合并多个 Expression 条件
+        if (dataPermissionExpression == null) {
+            return currentExpression;
+        }
         if (currentExpression == null) {
-            return equalsTo;
+            return dataPermissionExpression;
         }
-        // 如果表达式为 Or,则需要 (currentExpression) AND equalsTo
+        // 如果表达式为 Or,则需要 (currentExpression) AND dataPermissionExpression
         if (currentExpression instanceof OrExpression) {
-            return new AndExpression(new Parenthesis(currentExpression), equalsTo);
+            return new AndExpression(new Parenthesis(currentExpression), dataPermissionExpression);
         }
-        // 如果表达式为 And,则直接返回 currentExpression AND equalsTo
-        return new AndExpression(currentExpression, equalsTo);
+        // ② 如果表达式为 And,则直接返回 where AND dataPermissionExpression
+        return new AndExpression(currentExpression, dataPermissionExpression);
     }
 
     /**

+ 205 - 42
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java

@@ -46,7 +46,7 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
 
             @Override
             public Set<String> getTableNames() {
-                return asSet("entity", "entity1", "entity2", "t1", "t2", // 支持 MyBatis Plus 的单元测试
+                return asSet("entity", "entity1", "entity2", "entity3", "t1", "t2", "sys_dict_item", // 支持 MyBatis Plus 的单元测试
                         "t_user", "t_role"); // 满足自己的单元测试
             }
 
@@ -84,30 +84,30 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
     @Test
     void delete() {
         assertSql("delete from entity where id = ?",
-                "DELETE FROM entity WHERE id = ? AND tenant_id = 1");
+                "DELETE FROM entity WHERE id = ? AND entity.tenant_id = 1");
     }
 
     @Test
     void update() {
         assertSql("update entity set name = ? where id = ?",
-                "UPDATE entity SET name = ? WHERE id = ? AND tenant_id = 1");
+                "UPDATE entity SET name = ? WHERE id = ? AND entity.tenant_id = 1");
     }
 
     @Test
     void selectSingle() {
         // 单表
         assertSql("select * from entity where id = ?",
-                "SELECT * FROM entity WHERE id = ? AND tenant_id = 1");
+                "SELECT * FROM entity WHERE id = ? AND entity.tenant_id = 1");
 
         assertSql("select * from entity where id = ? or name = ?",
-                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND tenant_id = 1");
+                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
 
         assertSql("SELECT * FROM entity WHERE (id = ? OR name = ?)",
-                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND tenant_id = 1");
+                "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
 
         /* not */
         assertSql("SELECT * FROM entity WHERE not (id = ? OR name = ?)",
-                "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND tenant_id = 1");
+                "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND entity.tenant_id = 1");
     }
 
     @Test
@@ -167,10 +167,12 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
         assertSql("SELECT * FROM entity e WHERE e.id >= (select e1.id from entity1 e1 where e1.id = ?)",
                 "SELECT * FROM entity e WHERE e.id >= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
 
+
         /* <= */
         assertSql("SELECT * FROM entity e WHERE e.id <= (select e1.id from entity1 e1 where e1.id = ?)",
                 "SELECT * FROM entity e WHERE e.id <= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
 
+
         /* <> */
         assertSql("SELECT * FROM entity e WHERE e.id <> (select e1.id from entity1 e1 where e1.id = ?)",
                 "SELECT * FROM entity e WHERE e.id <> (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
@@ -204,6 +206,14 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
                 "SELECT * FROM entity e " +
                         "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
                         "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e " +
+                        "left join entity1 e1 on e1.id = e.id " +
+                        "left join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
+                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
+                        "WHERE e.tenant_id = 1");
     }
 
     @Test
@@ -212,17 +222,125 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
         assertSql("SELECT * FROM entity e " +
                         "right join entity1 e1 on e1.id = e.id",
                 "SELECT * FROM entity e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE e.tenant_id = 1");
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+                        "WHERE e1.tenant_id = 1");
+
+        assertSql("SELECT * FROM with_as_1 e " +
+                        "right join entity1 e1 on e1.id = e.id",
+                "SELECT * FROM with_as_1 e " +
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id " +
+                        "WHERE e1.tenant_id = 1");
 
         assertSql("SELECT * FROM entity e " +
                         "right join entity1 e1 on e1.id = e.id " +
                         "WHERE e.id = ? OR e.name = ?",
                 "SELECT * FROM entity e " +
-                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e " +
+                        "right join entity1 e1 on e1.id = e.id " +
+                        "right join entity2 e2 on e1.id = e2.id ",
+                "SELECT * FROM entity e " +
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+                        "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " +
+                        "WHERE e2.tenant_id = 1");
+    }
+
+    @Test
+    void selectMixJoin() {
+        assertSql("SELECT * FROM entity e " +
+                        "right join entity1 e1 on e1.id = e.id " +
+                        "left join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
+                        "WHERE e1.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e " +
+                        "left join entity1 e1 on e1.id = e.id " +
+                        "right join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
+                        "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " +
+                        "WHERE e2.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e " +
+                        "left join entity1 e1 on e1.id = e.id " +
+                        "inner join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
+                        "INNER JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 AND e2.tenant_id = 1");
     }
 
+
+    @Test
+    void selectJoinSubSelect() {
+        assertSql("select * from (select * from entity) e1 " +
+                        "left join entity2 e2 on e1.id = e2.id",
+                "SELECT * FROM (SELECT * FROM entity WHERE entity.tenant_id = 1) e1 " +
+                        "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1");
+
+        assertSql("select * from entity1 e1 " +
+                        "left join (select * from entity2) e2 " +
+                        "on e1.id = e2.id",
+                "SELECT * FROM entity1 e1 " +
+                        "LEFT JOIN (SELECT * FROM entity2 WHERE entity2.tenant_id = 1) e2 " +
+                        "ON e1.id = e2.id " +
+                        "WHERE e1.tenant_id = 1");
+    }
+
+    @Test
+    void selectSubJoin() {
+
+        assertSql("select * FROM " +
+                        "(entity1 e1 right JOIN entity2 e2 ON e1.id = e2.id)",
+                "SELECT * FROM " +
+                        "(entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
+                        "WHERE e2.tenant_id = 1");
+
+        assertSql("select * FROM " +
+                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id)",
+                "SELECT * FROM " +
+                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+                        "WHERE e1.tenant_id = 1");
+
+
+        assertSql("select * FROM " +
+                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id) " +
+                        "right join entity3 e3 on e1.id = e3.id",
+                "SELECT * FROM " +
+                        "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+                        "RIGHT JOIN entity3 e3 ON e1.id = e3.id AND e1.tenant_id = 1 " +
+                        "WHERE e3.tenant_id = 1");
+
+
+        assertSql("select * FROM entity e " +
+                        "LEFT JOIN (entity1 e1 right join entity2 e2 ON e1.id = e2.id) " +
+                        "on e.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN (entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
+                        "ON e.id = e2.id AND e2.tenant_id = 1 " +
+                        "WHERE e.tenant_id = 1");
+
+        assertSql("select * FROM entity e " +
+                        "LEFT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
+                        "on e.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "LEFT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+                        "ON e.id = e2.id AND e1.tenant_id = 1 " +
+                        "WHERE e.tenant_id = 1");
+
+        assertSql("select * FROM entity e " +
+                        "RIGHT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
+                        "on e.id = e2.id",
+                "SELECT * FROM entity e " +
+                        "RIGHT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+                        "ON e.id = e2.id AND e.tenant_id = 1 " +
+                        "WHERE e1.tenant_id = 1");
+    }
+
+
     @Test
     void selectLeftJoinMultipleTrailingOn() {
         // 多个 on 尾缀的
@@ -256,51 +374,97 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
                         "inner join entity1 e1 on e1.id = e.id " +
                         "WHERE e.id = ? OR e.name = ?",
                 "SELECT * FROM entity e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
+                        "WHERE e.id = ? OR e.name = ?");
 
         assertSql("SELECT * FROM entity e " +
                         "inner join entity1 e1 on e1.id = e.id " +
                         "WHERE (e.id = ? OR e.name = ?)",
                 "SELECT * FROM entity e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
+                        "WHERE (e.id = ? OR e.name = ?)");
+
+        // 隐式内连接
+        assertSql("SELECT * FROM entity,entity1 " +
+                        "WHERE entity.id = entity1.id",
+                "SELECT * FROM entity, entity1 " +
+                        "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
+
+        // 隐式内连接
+        assertSql("SELECT * FROM entity a, with_as_entity1 b " +
+                        "WHERE a.id = b.id",
+                "SELECT * FROM entity a, with_as_entity1 b " +
+                        "WHERE a.id = b.id AND a.tenant_id = 1");
+
+        assertSql("SELECT * FROM with_as_entity a, with_as_entity1 b " +
+                        "WHERE a.id = b.id",
+                "SELECT * FROM with_as_entity a, with_as_entity1 b " +
+                        "WHERE a.id = b.id");
+
+        // SubJoin with 隐式内连接
+        assertSql("SELECT * FROM (entity,entity1) " +
+                        "WHERE entity.id = entity1.id",
+                "SELECT * FROM (entity, entity1) " +
+                        "WHERE entity.id = entity1.id " +
+                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
+
+        assertSql("SELECT * FROM ((entity,entity1),entity2) " +
+                        "WHERE entity.id = entity1.id and entity.id = entity2.id",
+                "SELECT * FROM ((entity, entity1), entity2) " +
+                        "WHERE entity.id = entity1.id AND entity.id = entity2.id " +
+                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
+
+        assertSql("SELECT * FROM (entity,(entity1,entity2)) " +
+                        "WHERE entity.id = entity1.id and entity.id = entity2.id",
+                "SELECT * FROM (entity, (entity1, entity2)) " +
+                        "WHERE entity.id = entity1.id AND entity.id = entity2.id " +
+                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
+
+        // 沙雕的括号写法
+        assertSql("SELECT * FROM (((entity,entity1))) " +
+                        "WHERE entity.id = entity1.id",
+                "SELECT * FROM (((entity, entity1))) " +
+                        "WHERE entity.id = entity1.id " +
+                        "AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
 
-        // 垃圾 inner join todo
-//        assertSql("SELECT * FROM entity,entity1 " +
-//                "WHERE entity.id = entity1.id",
-//            "SELECT * FROM entity e " +
-//                "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-//                "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
     }
 
+
     @Test
     void selectWithAs() {
         assertSql("with with_as_A as (select * from entity) select * from with_as_A",
-                "WITH with_as_A AS (SELECT * FROM entity WHERE tenant_id = 1) SELECT * FROM with_as_A");
+                "WITH with_as_A AS (SELECT * FROM entity WHERE entity.tenant_id = 1) SELECT * FROM with_as_A");
+    }
+
+
+    @Test
+    void selectIgnoreTable() {
+        assertSql(" SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)",
+                "SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id AND item.tenant_id = 1 WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)");
     }
 
     private void assertSql(String sql, String targetSql) {
         assertEquals(targetSql, interceptor.parserSingle(sql, null));
     }
 
+
     // ========== 额外的测试 ==========
 
     @Test
     public void testSelectSingle() {
         // 单表
         assertSql("select * from t_user where id = ?",
-                "SELECT * FROM t_user WHERE id = ? AND tenant_id = 1 AND dept_id IN (10, 20)");
+                "SELECT * FROM t_user WHERE id = ? AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
 
         assertSql("select * from t_user where id = ? or name = ?",
-                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
 
         assertSql("SELECT * FROM t_user WHERE (id = ? OR name = ?)",
-                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+                "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
 
         /* not */
         assertSql("SELECT * FROM t_user WHERE not (id = ? OR name = ?)",
-                "SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+                "SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
     }
 
     @Test
@@ -329,16 +493,16 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
                         "right join t_role e1 on e1.id = e.id " +
                         "WHERE e.id = ? OR e.name = ?",
                 "SELECT * FROM t_user e " +
-                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
+                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
 
         // 条件 e.id = ? OR e.name = ? 带括号
         assertSql("SELECT * FROM t_user e " +
                         "right join t_role e1 on e1.id = e.id " +
                         "WHERE (e.id = ? OR e.name = ?)",
                 "SELECT * FROM t_user e " +
-                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+                        "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
+                        "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
     }
 
     @Test
@@ -348,23 +512,22 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
                         "inner join entity1 e1 on e1.id = e.id " +
                         "WHERE e.id = ? OR e.name = ?",
                 "SELECT * FROM t_user e " +
-                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
+                        "WHERE e.id = ? OR e.name = ?");
 
         // 条件 e.id = ? OR e.name = ? 带括号
         assertSql("SELECT * FROM t_user e " +
-                        "inner join t_role e1 on e1.id = e.id " +
+                        "inner join entity1 e1 on e1.id = e.id " +
                         "WHERE (e.id = ? OR e.name = ?)",
                 "SELECT * FROM t_user e " +
-                        "INNER JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-                        "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
-
-        // 垃圾 inner join todo
-//        assertSql("SELECT * FROM entity,entity1 " +
-//                "WHERE entity.id = entity1.id",
-//            "SELECT * FROM entity e " +
-//                "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-//                "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+                        "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
+                        "WHERE (e.id = ? OR e.name = ?)");
+
+        // 没有 On 的 inner join
+        assertSql("SELECT * FROM entity,entity1 " +
+                "WHERE entity.id = entity1.id",
+            "SELECT * FROM entity, entity1 " +
+                    "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
     }
 
 }

+ 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

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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


BIN
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

+ 5 - 1
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.SortingField;
 import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -78,7 +79,10 @@ public class MyBatisUtils {
      * @return Column 对象
      */
     public static Column buildColumn(String tableName, Alias tableAlias, String column) {
-        return new Column(tableAlias != null ? tableAlias.getName() + "." + column : column);
+        if (tableAlias != null) {
+            tableName = tableAlias.getName();
+        }
+        return new Column(tableName + StringPool.DOT + column);
     }
 
 }

+ 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));
     }
 

+ 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 类型.

+ 6 - 2
yudao-module-mall/pom.xml

@@ -15,14 +15,18 @@
     <name>${project.artifactId}</name>
 
     <description>
-        market模块,主要实现营销相关功能
-        例如:营销活动、banner广告、优惠券、优惠码等功能。
+        商城大模块,由 product 商品、market 营销、trade 交易 coupon等组成
     </description>
     <modules>
+        <module>yudao-module-coupon-api</module>
+        <module>yudao-module-coupon-biz</module>
         <module>yudao-module-market-api</module>
         <module>yudao-module-market-biz</module>
         <module>yudao-module-product-api</module>
         <module>yudao-module-product-biz</module>
+        <module>yudao-module-trade-api</module>
+        <module>yudao-module-trade-biz</module>
+
     </modules>
 
 </project>

+ 35 - 0
yudao-module-mall/yudao-module-coupon-api/pom.xml

@@ -0,0 +1,35 @@
+<?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-module-mall</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-module-coupon-api</artifactId>
+    <packaging>jar</packaging>
+
+
+    <name>${project.artifactId}</name>
+    <description>
+        coupon 模块 API,暴露给其它模块调用
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+
+        <!-- 参数校验 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

+ 29 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponExpireTimeTypeEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+
+/**
+ * 优惠券 - 是否开启过期提醒
+ *
+ * @author Sin
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CouponExpireTimeTypeEnum {
+
+    OPEN(1,"不开启"),
+    CLOSE(0,"开启"),;
+
+    /**
+     * 是否开启过期提醒
+     */
+    private final Integer type;
+    /**
+     * 是否开启过期提醒
+     */
+    private final String name;
+
+}

+ 29 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponFetchTypeEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+
+/**
+ * 优惠券 - 领取是否无限制 0
+ *
+ * @author Sin
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CouponFetchTypeEnum {
+
+    LIMIT(1,"限制"),
+    NOT_LIMIT(0,"不限制"),;
+
+    /**
+     * 是否开启过期提醒
+     */
+    private final Integer type;
+    /**
+     * 是否开启过期提醒
+     */
+    private final String name;
+
+}

+ 29 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponForbidPreferenceEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+
+/**
+ * 优惠券 - 优惠叠加类型
+ *
+ * @author Sin
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CouponForbidPreferenceEnum {
+
+    UN_FORBID(0,"不限制"),
+    FORBID(1,"优惠券仅原价购买商品时可用");
+
+    /**
+     * 优惠券类型
+     */
+    private final Integer type;
+    /**
+     * 优惠券类型名
+     */
+    private final String name;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponGoodsTypeEnum.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+
+/**
+ * 优惠券 - 优惠券商品使用类型
+ *
+ * @author Sin
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CouponGoodsTypeEnum {
+
+    ALL(1,"全部商品可用"),
+    POINT_PRODUCT(2,"指定商品可用"),
+    POINT_PRODUCT_NOT(3,"指定商品不可用"),;
+
+    /**
+     * 优惠券商品使用类型
+     */
+    private final Integer type;
+    /**
+     * 优惠券商品使用类型名
+     */
+    private final String name;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponStatusTypeEnum.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+
+/**
+ * 优惠券 - 优惠券状态类型
+ *
+ * @author Sin
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CouponStatusTypeEnum {
+
+    PROCESSING(1,"进行中"),
+    END(2,"已结束"),
+    CLOSE(3,"已关闭"),;
+
+    /**
+     * 优惠券类型
+     */
+    private final Integer type;
+    /**
+     * 优惠券类型名
+     */
+    private final String name;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponTypeEnum.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+
+/**
+ * 优惠券 - 优惠券类型
+ *
+ * @author Sin
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CouponTypeEnum {
+
+    REWARD(1,"满减"),
+    DISCOUNT(2,"折扣"),
+    RANDOW(3,"随机"),;
+
+    /**
+     * 优惠券类型
+     */
+    private final Integer type;
+    /**
+     * 优惠券类型名
+     */
+    private final String name;
+
+}

+ 29 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponUseLimitEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+
+/**
+ * 优惠券使用类型 - 优惠券使用类型类型
+ *
+ * @author Sin
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CouponUseLimitEnum {
+
+    HAS_LIMIT(1,"无门槛"),
+    NO_LIMIT(2,"有门槛"),;
+
+    /**
+     * 优惠券使用类型
+     */
+    private final Integer type;
+    /**
+     * 优惠券使用类型名
+     */
+    private final String name;
+
+}

+ 29 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/CouponValidityTypeEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 过期类型 - 状态
+ *
+ * @author Sin
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CouponValidityTypeEnum {
+
+    TIME_RANGE_EXPIRTED(1,"时间范围过期"),
+    EXPIRES_AFTER_FIXED_DATE(2,"领取之日固定日期后过期"),
+    EXPIRES_DATE_NEXT_FIEXD_DATE(3,"领取次日固定日期后过期"),;
+
+
+    /**
+     * 状态值
+     */
+    private final Integer status;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+}

+ 27 - 0
yudao-module-mall/yudao-module-coupon-api/src/main/java/cn/iocoder/yudao/module/CouponTemplete/enums/ErrorCodeConstants.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.CouponTemplete.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * coupon 优惠券错误码枚举类
+ *
+ * coupon 优惠券系统,使用 1-010-000-000 段
+ */
+public interface ErrorCodeConstants {
+    // ========== COUPON分类相关 1010001000 ============
+
+    ErrorCode COUPON_TEMPLETE_NOT_EXISTS = new ErrorCode(1010001000, "优惠券模板不存在");
+    ErrorCode MONEY_NOT_NULL = new ErrorCode(1010001001, "当type为reward时需要添加发放面额不能为空");
+    ErrorCode DISCOUNT_NOT_NULL = new ErrorCode(1010001001, "当type为discount时需要添加折扣不能为空");
+    ErrorCode DISCOUNT_LIMIT_NOT_NULL = new ErrorCode(1010001001, "当type为discount时可选择性添加最多折扣金额不能为空");
+    ErrorCode MIN_MAX_NOT_NULL = new ErrorCode(1010001001, "当type为radom时需要添加最低金额");
+    ErrorCode START_END_TIME_NOT_NULL = new ErrorCode(1010001001, "使用开始日期,使用结束日期不能为空");
+    ErrorCode FIXED_TERM_NOT_NULL = new ErrorCode(1010001001, "领取之日起或者次日N天内有效不能为空");
+
+
+    // ========== COUPON分类相关 1010002000 ============
+    ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1010001000, "优惠券模板不存在");
+
+
+}
+

+ 62 - 0
yudao-module-mall/yudao-module-coupon-biz/pom.xml

@@ -0,0 +1,62 @@
+<?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-module-mall</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>yudao-module-coupon-biz</artifactId>
+
+    <name>${project.artifactId}</name>
+
+    <description>
+        coupon模块,主要负责优惠券的一些业务,含发布优惠券模板,分发优惠券等
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-coupon-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
+        </dependency>
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-excel</artifactId>
+        </dependency>
+
+        <!-- DB 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 88 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/CouponController.java

@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.coupon;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.annotations.*;
+
+import javax.validation.*;
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+
+import cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo.*;
+import cn.iocoder.yudao.module.coupon.dal.dataobject.coupon.CouponDO;
+import cn.iocoder.yudao.module.coupon.convert.coupon.CouponConvert;
+import cn.iocoder.yudao.module.coupon.service.coupon.CouponService;
+
+@Api(tags = "管理后台 - 优惠券")
+@RestController
+@RequestMapping("/coupon/item")
+@Validated
+public class CouponController {
+
+    @Resource
+    private CouponService couponService;
+
+
+    //todo 用户优惠券
+    @PostMapping("/create")
+    @ApiOperation("用户领取优惠券")
+    @PreAuthorize("@ss.hasPermission('coupon::create')")
+    public CommonResult<Long> create(@RequestParam("couponTemplateId") Long couponTemplateId) {
+
+        return success(couponService.create(couponTemplateId));
+    }
+
+
+    @PutMapping("/update")
+    @ApiOperation("更新优惠券")
+    @PreAuthorize("@ss.hasPermission('coupon::update')")
+    public CommonResult<Boolean> update(@Valid @RequestBody CouponUpdateReqVO updateReqVO) {
+        couponService.update(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除优惠券")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('coupon::delete')")
+    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
+        couponService.delete(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得优惠券")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('coupon::query')")
+    public CommonResult<CouponRespVO> get(@RequestParam("id") Long id) {
+        CouponDO couponDO = couponService.get(id);
+        return success(CouponConvert.INSTANCE.convert(couponDO));
+    }
+
+
+
+    @GetMapping("/list")
+    @ApiOperation("获得优惠券列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+    @PreAuthorize("@ss.hasPermission('coupon::query')")
+    public CommonResult<List<CouponRespVO>> getList(@RequestParam("ids") Collection<Long> ids) {
+        List<CouponDO> list = couponService.getList(ids);
+        return success(CouponConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得优惠券分页")
+    @PreAuthorize("@ss.hasPermission('coupon::query')")
+    public CommonResult<PageResult<CouponRespVO>> getPage(@Valid CouponPageReqVO pageVO) {
+        PageResult<CouponDO> pageResult = couponService.getPage(pageVO);
+        return success(CouponConvert.INSTANCE.convertPage(pageResult));
+    }
+
+
+}

+ 110 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponBaseVO.java

@@ -0,0 +1,110 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo;
+
+import lombok.*;
+import java.util.*;
+import java.math.BigDecimal;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+* 优惠券 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class CouponBaseVO {
+
+    @ApiModelProperty(value = "优惠券类型 reward-满减 discount-折扣 random-随机", required = true)
+    @NotNull(message = "优惠券类型 reward-满减 discount-折扣 random-随机不能为空")
+    private String type;
+
+    @ApiModelProperty(value = "优惠券名称", required = true)
+    @NotNull(message = "优惠券名称不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "优惠券类型id")
+    private Long couponTypeId;
+
+    @ApiModelProperty(value = "优惠券编码", required = true)
+    @NotNull(message = "优惠券编码不能为空")
+    private String couponCode;
+
+    @ApiModelProperty(value = "领用人", required = true)
+    @NotNull(message = "领用人不能为空")
+    private Long memberId;
+
+    @ApiModelProperty(value = "优惠券使用订单id", required = true)
+    @NotNull(message = "优惠券使用订单id不能为空")
+    private Long useOrderId;
+
+    @ApiModelProperty(value = "适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用", required = true)
+    @NotNull(message = "适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用不能为空")
+    private Boolean goodsType;
+
+    @ApiModelProperty(value = "适用商品id", required = true)
+    @NotNull(message = "适用商品id不能为空")
+    private String goodsIds;
+
+    @ApiModelProperty(value = "最小金额", required = true)
+    @NotNull(message = "最小金额不能为空")
+    private BigDecimal atLeast;
+
+    @ApiModelProperty(value = "面额", required = true)
+    @NotNull(message = "面额不能为空")
+    private BigDecimal money;
+
+    @ApiModelProperty(value = "1 =< 折扣 <= 9.9 当type为discount时需要添加", required = true)
+    @NotNull(message = "1 =< 折扣 <= 9.9 当type为discount时需要添加不能为空")
+    private BigDecimal discount;
+
+    @ApiModelProperty(value = "最多折扣金额 当type为discount时可选择性添加", required = true)
+    @NotNull(message = "最多折扣金额 当type为discount时可选择性添加不能为空")
+    private BigDecimal discountLimit;
+
+    @ApiModelProperty(value = "优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用", required = true)
+    @NotNull(message = "优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用不能为空")
+    private Boolean whetherForbidPreference;
+
+    @ApiModelProperty(value = "是否开启过期提醒0-不开启 1-开启", required = true)
+    @NotNull(message = "是否开启过期提醒0-不开启 1-开启")
+    private Boolean whetherExpireNotice;
+
+    @ApiModelProperty(value = "过期前N天提醒", required = true)
+    @NotNull(message = "过期前N天提醒不能为空")
+    private Integer expireNoticeFixedTerm;
+
+    @ApiModelProperty(value = "是否已提醒", required = true)
+    @NotNull(message = "是否已提醒不能为空")
+    private Boolean whetherNoticed;
+
+    @ApiModelProperty(value = "优惠券状态 1已领用(未使用) 2已使用 3已过期", required = true)
+    @NotNull(message = "优惠券状态 1已领用(未使用) 2已使用 3已过期不能为空")
+    private Integer state;
+
+    @ApiModelProperty(value = "获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取", required = true)
+    @NotNull(message = "获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取不能为空")
+    private Boolean getType;
+
+    @ApiModelProperty(value = "领取时间", required = true)
+    @NotNull(message = "领取时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date fetchTime;
+
+    @ApiModelProperty(value = "使用时间", required = true)
+    @NotNull(message = "使用时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date useTime;
+
+    @ApiModelProperty(value = "可使用的开始时间", required = true)
+    @NotNull(message = "可使用的开始时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date startTime;
+
+    @ApiModelProperty(value = "有效期结束时间", required = true)
+    @NotNull(message = "有效期结束时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date endTime;
+
+}

+ 3 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateReqVO.java → yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponCreateReqVO.java

@@ -1,14 +1,14 @@
-package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+package cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo;
 
 import lombok.*;
 import java.util.*;
 import io.swagger.annotations.*;
 import javax.validation.constraints.*;
 
-@ApiModel("管理后台 - 商品sku创建 Request VO")
+@ApiModel("管理后台 - 优惠券创建 Request VO")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class ProductSkuCreateReqVO extends ProductSkuBaseVO {
+public class CouponCreateReqVO extends CouponBaseVO {
 
 }

+ 90 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponExcelVO.java

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo;
+
+import lombok.*;
+import java.util.*;
+import java.math.BigDecimal;
+import io.swagger.annotations.*;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+/**
+ * 优惠券 Excel VO
+ *
+ * @author wxr
+ */
+@Data
+public class CouponExcelVO {
+
+    @ExcelProperty("用户ID")
+    private Long id;
+
+    @ExcelProperty("优惠券类型 reward-满减 discount-折扣 random-随机")
+    private String type;
+
+    @ExcelProperty("优惠券名称")
+    private String name;
+
+    @ExcelProperty("优惠券类型id")
+    private Long couponTypeId;
+
+    @ExcelProperty("优惠券编码")
+    private String couponCode;
+
+    @ExcelProperty("领用人")
+    private Long memberId;
+
+    @ExcelProperty("优惠券使用订单id")
+    private Long useOrderId;
+
+    @ExcelProperty("适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用")
+    private Boolean goodsType;
+
+    @ExcelProperty("适用商品id")
+    private String goodsIds;
+
+    @ExcelProperty("最小金额")
+    private BigDecimal atLeast;
+
+    @ExcelProperty("面额")
+    private BigDecimal money;
+
+    @ExcelProperty("1 =< 折扣 <= 9.9 当type为discount时需要添加")
+    private BigDecimal discount;
+
+    @ExcelProperty("最多折扣金额 当type为discount时可选择性添加")
+    private BigDecimal discountLimit;
+
+    @ExcelProperty("优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用")
+    private Boolean whetherForbidPreference;
+
+    @ExcelProperty("是否开启过期提醒0-不开启 1-开启")
+    private Boolean whetherExpireNotice;
+
+    @ExcelProperty("过期前N天提醒")
+    private Integer expireNoticeFixedTerm;
+
+    @ExcelProperty("是否已提醒")
+    private Boolean whetherNoticed;
+
+    @ExcelProperty("优惠券状态 1已领用(未使用) 2已使用 3已过期")
+    private Integer state;
+
+    @ExcelProperty("获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取")
+    private Boolean getType;
+
+    @ExcelProperty("领取时间")
+    private Date fetchTime;
+
+    @ExcelProperty("使用时间")
+    private Date useTime;
+
+    @ExcelProperty("可使用的开始时间")
+    private Date startTime;
+
+    @ExcelProperty("有效期结束时间")
+    private Date endTime;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+}

+ 91 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponExportReqVO.java

@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo;
+
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "管理后台 - 优惠券 Excel 导出 Request VO", description = "参数和 CouponPageReqVO 是一致的")
+@Data
+public class CouponExportReqVO {
+
+    @ApiModelProperty(value = "优惠券类型 reward-满减 discount-折扣 random-随机")
+    private String type;
+
+    @ApiModelProperty(value = "优惠券名称")
+    private String name;
+
+    @ApiModelProperty(value = "优惠券类型id")
+    private Long couponTypeId;
+
+    @ApiModelProperty(value = "优惠券编码")
+    private String couponCode;
+
+    @ApiModelProperty(value = "领用人")
+    private Long memberId;
+
+    @ApiModelProperty(value = "优惠券使用订单id")
+    private Long useOrderId;
+
+    @ApiModelProperty(value = "适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用")
+    private Boolean goodsType;
+
+    @ApiModelProperty(value = "适用商品id")
+    private String goodsIds;
+
+    @ApiModelProperty(value = "最小金额")
+    private BigDecimal atLeast;
+
+    @ApiModelProperty(value = "面额")
+    private BigDecimal money;
+
+    @ApiModelProperty(value = "1 =< 折扣 <= 9.9 当type为discount时需要添加")
+    private BigDecimal discount;
+
+    @ApiModelProperty(value = "最多折扣金额 当type为discount时可选择性添加")
+    private BigDecimal discountLimit;
+
+    @ApiModelProperty(value = "优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用")
+    private Boolean whetherForbidPreference;
+
+    @ApiModelProperty(value = "是否开启过期提醒0-不开启 1-开启")
+    private Boolean whetherExpireNotice;
+
+    @ApiModelProperty(value = "过期前N天提醒")
+    private Integer expireNoticeFixedTerm;
+
+    @ApiModelProperty(value = "是否已提醒")
+    private Boolean whetherNoticed;
+
+    @ApiModelProperty(value = "优惠券状态 1已领用(未使用) 2已使用 3已过期")
+    private Integer state;
+
+    @ApiModelProperty(value = "获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取")
+    private Boolean getType;
+
+    @ApiModelProperty(value = "领取时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] fetchTime;
+
+    @ApiModelProperty(value = "使用时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] useTime;
+
+    @ApiModelProperty(value = "可使用的开始时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] startTime;
+
+    @ApiModelProperty(value = "有效期结束时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] endTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] createTime;
+
+}

+ 93 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponPageReqVO.java

@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo;
+
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 优惠券分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "优惠券类型 reward-满减 discount-折扣 random-随机")
+    private String type;
+
+    @ApiModelProperty(value = "优惠券名称")
+    private String name;
+
+    @ApiModelProperty(value = "优惠券类型id")
+    private Long couponTypeId;
+
+    @ApiModelProperty(value = "优惠券编码")
+    private String couponCode;
+
+    @ApiModelProperty(value = "领用人")
+    private Long memberId;
+
+    @ApiModelProperty(value = "优惠券使用订单id")
+    private Long useOrderId;
+
+    @ApiModelProperty(value = "适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用")
+    private Boolean goodsType;
+
+    @ApiModelProperty(value = "适用商品id")
+    private String goodsIds;
+
+    @ApiModelProperty(value = "最小金额")
+    private BigDecimal atLeast;
+
+    @ApiModelProperty(value = "面额")
+    private BigDecimal money;
+
+    @ApiModelProperty(value = "1 =< 折扣 <= 9.9 当type为discount时需要添加")
+    private BigDecimal discount;
+
+    @ApiModelProperty(value = "最多折扣金额 当type为discount时可选择性添加")
+    private BigDecimal discountLimit;
+
+    @ApiModelProperty(value = "优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用")
+    private Boolean whetherForbidPreference;
+
+    @ApiModelProperty(value = "是否开启过期提醒0-不开启 1-开启")
+    private Boolean whetherExpireNotice;
+
+    @ApiModelProperty(value = "过期前N天提醒")
+    private Integer expireNoticeFixedTerm;
+
+    @ApiModelProperty(value = "是否已提醒")
+    private Boolean whetherNoticed;
+
+    @ApiModelProperty(value = "优惠券状态 1已领用(未使用) 2已使用 3已过期")
+    private Integer state;
+
+    @ApiModelProperty(value = "获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取")
+    private Boolean getType;
+
+    @ApiModelProperty(value = "领取时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] fetchTime;
+
+    @ApiModelProperty(value = "使用时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] useTime;
+
+    @ApiModelProperty(value = "可使用的开始时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] startTime;
+
+    @ApiModelProperty(value = "有效期结束时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] endTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] createTime;
+
+}

+ 4 - 4
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/controller/admin/activity/vo/ActivityRespVO.java → yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponRespVO.java

@@ -1,16 +1,16 @@
-package cn.iocoder.yudao.module.market.controller.admin.activity.vo;
+package cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo;
 
 import lombok.*;
 import java.util.*;
 import io.swagger.annotations.*;
 
-@ApiModel("管理后台 - 促销活动 Response VO")
+@ApiModel("管理后台 - 优惠券 Response VO")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class ActivityRespVO extends ActivityBaseVO {
+public class CouponRespVO extends CouponBaseVO {
 
-    @ApiModelProperty(value = "活动编号", required = true)
+    @ApiModelProperty(value = "用户ID", required = true)
     private Long id;
 
     @ApiModelProperty(value = "创建时间", required = true)

+ 18 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/coupon/vo/CouponUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 优惠券更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponUpdateReqVO extends CouponBaseVO {
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    @NotNull(message = "用户ID不能为空")
+    private Long id;
+
+}

+ 81 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/CouponTempleteController.java

@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.templete;
+
+import cn.iocoder.yudao.module.coupon.controller.admin.templete.vo.*;
+import cn.iocoder.yudao.module.coupon.convert.CouponTemplete.CouponTempleteConvert;
+import cn.iocoder.yudao.module.coupon.dal.dataobject.CouponTemplete.CouponTempleteDO;
+import cn.iocoder.yudao.module.coupon.service.CouponTemplete.CouponTempleteService;
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.annotations.*;
+
+import javax.validation.*;
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 优惠券模板")
+@RestController
+@RequestMapping("/coupon/template")
+@Validated
+public class CouponTempleteController {
+
+    @Resource
+    private CouponTempleteService couponTempleteServiceService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建优惠券模板")
+    @PreAuthorize("@ss.hasPermission('CouponTemplete::create')")
+    public CommonResult<Long> create(@Valid @RequestBody CouponTempleteCreateReqVO createReqVO) {
+        return success(couponTempleteServiceService.create(createReqVO));
+    }
+
+//    @PutMapping("/update")
+//    @ApiOperation("更新优惠券模板")
+//    @PreAuthorize("@ss.hasPermission('CouponTemplete::update')")
+//    public CommonResult<Boolean> update(@Valid @RequestBody CouponTempleteUpdateReqVO updateReqVO) {
+//        couponTempleteServiceService.update(updateReqVO);
+//        return success(true);
+//    }
+//
+//    @DeleteMapping("/delete")
+//    @ApiOperation("删除优惠券模板")
+//    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+//    @PreAuthorize("@ss.hasPermission('CouponTemplete::delete')")
+//    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
+//        couponTempleteServiceService.delete(id);
+//        return success(true);
+//    }
+//
+//    @GetMapping("/get")
+//    @ApiOperation("获得优惠券模板")
+//    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+//    @PreAuthorize("@ss.hasPermission('CouponTemplete::query')")
+//    public CommonResult<CouponTempleteRespVO> get(@RequestParam("id") Long id) {
+//        CouponTempleteDO couponTempleteDO = couponTempleteServiceService.get(id);
+//        return success(CouponTempleteConvert.INSTANCE.convert(couponTempleteDO));
+//    }
+//
+//    @GetMapping("/list")
+//    @ApiOperation("获得优惠券模板列表")
+//    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+//    @PreAuthorize("@ss.hasPermission('CouponTemplete::query')")
+//    public CommonResult<List<CouponTempleteRespVO>> getList(@RequestParam("ids") Collection<Long> ids) {
+//        List<CouponTempleteDO> list = couponTempleteServiceService.getList(ids);
+//        return success(CouponTempleteConvert.INSTANCE.convertList(list));
+//    }
+//
+    @GetMapping("/page")
+    @ApiOperation("获得优惠券模板分页")
+    @PreAuthorize("@ss.hasPermission('CouponTemplete::query')")
+    public CommonResult<PageResult<CouponTempleteRespVO>> getPage(@Valid CouponTempletePageReqVO pageVO) {
+        PageResult<CouponTempleteDO> pageResult = couponTempleteServiceService.getPage(pageVO);
+        return success(CouponTempleteConvert.INSTANCE.convertPage(pageResult));
+    }
+
+
+
+}

+ 172 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteBaseVO.java

@@ -0,0 +1,172 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.templete.vo;
+
+import lombok.*;
+import java.util.*;
+import java.math.BigDecimal;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+* 优惠券模板 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class CouponTempleteBaseVO {
+
+
+    //基本信息
+
+    @ApiModelProperty(value = "优惠券名称", required = true)
+    @NotNull(message = "优惠券名称不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "名称备注")
+    private String couponNameRemark;
+
+    @ApiModelProperty(value = "优惠券图片")
+    private String image;
+
+    /*  ============判断适用商品——开始=============  */
+
+
+    @ApiModelProperty(value = "适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用", required = true)
+    @NotNull(message = "适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用不能为空")
+    private Integer goodsType;
+
+    @ApiModelProperty(value = "适用商品id")
+    private String productIds;
+
+    @ApiModelProperty(value = "使用门槛0-无门槛 1-有门槛", required = true)
+    @NotNull(message = "使用门槛0-无门槛 1-有门槛不能为空")
+    private Boolean hasUseLimit;
+
+    @ApiModelProperty(value = "满多少元使用 0代表无限制", required = true)
+    @NotNull(message = "满多少元使用 0代表无限制不能为空")
+    private BigDecimal atLeast;
+
+
+    /*  ============折扣类型——开始=============  */
+
+    @ApiModelProperty(value = "优惠券类型 reward-满减 discount-折扣 random-随机", required = true)
+    @NotNull(message = "优惠券类型 reward-满减 discount-折扣 random-随机不能为空")
+    private String type;
+
+    @ApiModelProperty(value = "发放面额 当type为reward时需要添加")
+    @NotNull(message = "发放面额 当type为reward时需要添加不能为空")
+    private BigDecimal money;
+
+    @ApiModelProperty(value = "1 =< 折扣 <= 9.9 当type为discount时需要添加")
+    @NotNull(message = "1 =< 折扣 <= 9.9 当type为discount时需要添加不能为空")
+    private BigDecimal discount;
+
+    @ApiModelProperty(value = "最多折扣金额 当type为discount时可选择性添加")
+    @NotNull(message = "最多折扣金额 当type为discount时可选择性添加不能为空")
+    private BigDecimal discountLimit;
+
+    @ApiModelProperty(value = "最低金额 当type为radom时需要添加", required = true)
+    @NotNull(message = "最低金额 当type为radom时需要添加不能为空")
+    private BigDecimal minMoney;
+
+    @ApiModelProperty(value = "最大金额 当type为radom时需要添加", required = true)
+    @NotNull(message = "最大金额 当type为radom时需要添加不能为空")
+    private BigDecimal maxMoney;
+
+    /*  ============折扣类型——结束=============  */
+
+
+    /*  ============过期类型——开始=============  */
+
+
+    @ApiModelProperty(value = "过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期", required = true)
+    @NotNull(message = "过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期 不能为空")
+    private Integer validityType;
+
+    @ApiModelProperty(value = "使用开始日期 过期类型1时必填")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date startUseTime;
+
+    @ApiModelProperty(value = "使用结束日期 过期类型1时必填")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date endUseTime;
+
+    @ApiModelProperty(value = "当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效")
+    @NotNull(message = "当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效不能为空")
+    private Integer fixedTerm;
+
+    @ApiModelProperty(value = "有效日期结束时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date endTime;
+
+    /*  ============过期类型——结束=============  */
+
+
+    @ApiModelProperty(value = "领取是否无限制0-否 1是", required = true)
+    @NotNull(message = "是否无限制0-否 1是")
+    private Boolean whetherLimitless;
+
+    @ApiModelProperty(value = "每人最大领取个数", required = true)
+    @NotNull(message = "每人最大领取个数不能为空")
+    private Integer maxFetch;
+
+    @ApiModelProperty(value = "是否开启过期提醒 0-不开启 1-开启", required = true)
+    @NotNull(message = "是否开启过期提醒0-不开启 1-开启不能为空")
+    private Boolean whetherExpireNotice;
+
+    @ApiModelProperty(value = "过期前N天提醒", required = true)
+    @NotNull(message = "过期前N天提醒不能为空")
+    private Integer expireNoticeFixedTerm;
+
+    @ApiModelProperty(value = "优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用", required = true)
+    @NotNull(message = "优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用不能为空")
+    private Boolean whetherForbidPreference;
+
+    @ApiModelProperty(value = "是否显示", required = true)
+    @NotNull(message = "是否显示不能为空")
+    private Integer whetherShow;
+
+    @ApiModelProperty(value = "是否禁止发放0-否 1-是", required = true)
+    @NotNull(message = "是否禁止发放0-否 1-是不能为空")
+    private Boolean whetherForbidden;
+
+    /*  ============汇总计算——开始=============  */
+
+
+
+    @ApiModelProperty(value = "使用优惠券购买的商品数量", required = true)
+    @NotNull(message = "使用优惠券购买的商品数量不能为空")
+    private Integer orderGoodsNum;
+
+    @ApiModelProperty(value = "订单的优惠总金额", required = true)
+    @NotNull(message = "订单的优惠总金额不能为空")
+    private BigDecimal discountOrderMoney;
+
+    @ApiModelProperty(value = "用券总成交额", required = true)
+    @NotNull(message = "用券总成交额不能为空")
+    private BigDecimal orderMoney;
+
+    @ApiModelProperty(value = "发放数量", required = true)
+    @NotNull(message = "发放数量不能为空")
+    private Integer count;
+
+    @ApiModelProperty(value = "已领取数量", required = true)
+    @NotNull(message = "已领取数量不能为空")
+    private Integer leadCount;
+
+    @ApiModelProperty(value = "已使用数量", required = true)
+    @NotNull(message = "已使用数量不能为空")
+    private Integer usedCount;
+
+
+    /*  ============汇总计算——结束=============  */
+
+
+    @ApiModelProperty(value = "状态(1进行中2已结束3已关闭)", required = true)
+    @NotNull(message = "状态(1进行中2已结束-1已关闭)不能为空")
+    private Integer status;
+
+
+
+}

+ 12 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteCreateReqVO.java

@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.templete.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 优惠券模板创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponTempleteCreateReqVO extends CouponTempleteBaseVO {
+
+}

+ 119 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteExcelVO.java

@@ -0,0 +1,119 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.templete.vo;
+
+import lombok.*;
+import java.util.*;
+import java.math.BigDecimal;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+/**
+ * 优惠券模板 Excel VO
+ *
+ * @author wxr
+ */
+@Data
+public class CouponTempleteExcelVO {
+
+    @ExcelProperty("用户ID")
+    private Long id;
+
+    @ExcelProperty("优惠券类型 reward-满减 discount-折扣 random-随机")
+    private String type;
+
+    @ExcelProperty("优惠券名称")
+    private String name;
+
+    @ExcelProperty("名称备注")
+    private String couponNameRemark;
+
+    @ExcelProperty("优惠券图片")
+    private String image;
+
+    @ExcelProperty("发放数量")
+    private Integer count;
+
+    @ExcelProperty("已领取数量")
+    private Integer leadCount;
+
+    @ExcelProperty("已使用数量")
+    private Integer usedCount;
+
+    @ExcelProperty("适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用")
+    private Integer goodsType;
+
+    @ExcelProperty("适用商品id")
+    private String productIds;
+
+    @ExcelProperty("使用门槛0-无门槛 1-有门槛")
+    private Boolean hasUseLimit;
+
+    @ExcelProperty("满多少元使用 0代表无限制")
+    private BigDecimal atLeast;
+
+    @ExcelProperty("发放面额 当type为reward时需要添加")
+    private BigDecimal money;
+
+    @ExcelProperty("1 =< 折扣 <= 9.9 当type为discount时需要添加")
+    private BigDecimal discount;
+
+    @ExcelProperty("最多折扣金额 当type为discount时可选择性添加")
+    private BigDecimal discountLimit;
+
+    @ExcelProperty("最低金额 当type为radom时需要添加")
+    private BigDecimal minMoney;
+
+    @ExcelProperty("最大金额 当type为radom时需要添加")
+    private BigDecimal maxMoney;
+
+    @ExcelProperty("过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期")
+    private Integer validityType;
+
+    @ExcelProperty("使用开始日期 过期类型1时必填")
+    private Date startUseTime;
+
+    @ExcelProperty("使用结束日期 过期类型1时必填")
+    private Date endUseTime;
+
+    @ExcelProperty("当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效")
+    private Integer fixedTerm;
+
+    @ExcelProperty("是否无限制0-否 1是")
+    private Boolean whetherLimitless;
+
+    @ExcelProperty("每人最大领取个数")
+    private Integer maxFetch;
+
+    @ExcelProperty("是否开启过期提醒0-不开启 1-开启")
+    private Boolean whetherExpireNotice;
+
+    @ExcelProperty("过期前N天提醒")
+    private Integer expireNoticeFixedTerm;
+
+    @ExcelProperty("优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用")
+    private Boolean whetherForbidPreference;
+
+    @ExcelProperty("是否显示")
+    private Integer whetherShow;
+
+    @ExcelProperty("订单的优惠总金额")
+    private BigDecimal discountOrderMoney;
+
+    @ExcelProperty("用券总成交额")
+    private BigDecimal orderMoney;
+
+    @ExcelProperty("是否禁止发放0-否 1-是")
+    private Boolean whetherForbidden;
+
+    @ExcelProperty("使用优惠券购买的商品数量")
+    private Integer orderGoodsNum;
+
+    @ExcelProperty("状态(1进行中2已结束-1已关闭)")
+    private Integer status;
+
+    @ExcelProperty("有效日期结束时间")
+    private Date endTime;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+}

+ 119 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteExportReqVO.java

@@ -0,0 +1,119 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.templete.vo;
+
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.util.*;
+import io.swagger.annotations.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "管理后台 - 优惠券模板 Excel 导出 Request VO", description = "参数和 CouponTempletePageReqVO 是一致的")
+@Data
+public class CouponTempleteExportReqVO {
+
+    @ApiModelProperty(value = "优惠券类型 reward-满减 discount-折扣 random-随机")
+    private String type;
+
+    @ApiModelProperty(value = "优惠券名称")
+    private String name;
+
+    @ApiModelProperty(value = "名称备注")
+    private String couponNameRemark;
+
+    @ApiModelProperty(value = "优惠券图片")
+    private String image;
+
+    @ApiModelProperty(value = "发放数量")
+    private Integer count;
+
+    @ApiModelProperty(value = "已领取数量")
+    private Integer leadCount;
+
+    @ApiModelProperty(value = "已使用数量")
+    private Integer usedCount;
+
+    @ApiModelProperty(value = "适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用")
+    private Boolean goodsType;
+
+    @ApiModelProperty(value = "适用商品id")
+    private String productIds;
+
+    @ApiModelProperty(value = "使用门槛0-无门槛 1-有门槛")
+    private Boolean hasUseLimit;
+
+    @ApiModelProperty(value = "满多少元使用 0代表无限制")
+    private BigDecimal atLeast;
+
+    @ApiModelProperty(value = "发放面额 当type为reward时需要添加")
+    private BigDecimal money;
+
+    @ApiModelProperty(value = "1 =< 折扣 <= 9.9 当type为discount时需要添加")
+    private BigDecimal discount;
+
+    @ApiModelProperty(value = "最多折扣金额 当type为discount时可选择性添加")
+    private BigDecimal discountLimit;
+
+    @ApiModelProperty(value = "最低金额 当type为radom时需要添加")
+    private BigDecimal minMoney;
+
+    @ApiModelProperty(value = "最大金额 当type为radom时需要添加")
+    private BigDecimal maxMoney;
+
+    @ApiModelProperty(value = "过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期")
+    private Boolean validityType;
+
+    @ApiModelProperty(value = "使用开始日期 过期类型1时必填")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] startUseTime;
+
+    @ApiModelProperty(value = "使用结束日期 过期类型1时必填")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] endUseTime;
+
+    @ApiModelProperty(value = "当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效")
+    private Integer fixedTerm;
+
+    @ApiModelProperty(value = "是否无限制0-否 1是")
+    private Boolean whetherLimitless;
+
+    @ApiModelProperty(value = "每人最大领取个数")
+    private Integer maxFetch;
+
+    @ApiModelProperty(value = "是否开启过期提醒0-不开启 1-开启")
+    private Boolean whetherExpireNotice;
+
+    @ApiModelProperty(value = "过期前N天提醒")
+    private Integer expireNoticeFixedTerm;
+
+    @ApiModelProperty(value = "优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用")
+    private Boolean whetherForbidPreference;
+
+    @ApiModelProperty(value = "是否显示")
+    private Integer whetherShow;
+
+    @ApiModelProperty(value = "订单的优惠总金额")
+    private BigDecimal discountOrderMoney;
+
+    @ApiModelProperty(value = "用券总成交额")
+    private BigDecimal orderMoney;
+
+    @ApiModelProperty(value = "是否禁止发放0-否 1-是")
+    private Boolean whetherForbidden;
+
+    @ApiModelProperty(value = "使用优惠券购买的商品数量")
+    private Integer orderGoodsNum;
+
+    @ApiModelProperty(value = "状态(1进行中2已结束-1已关闭)")
+    private Integer status;
+
+    @ApiModelProperty(value = "有效日期结束时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] endTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] createTime;
+
+}

+ 122 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempletePageReqVO.java

@@ -0,0 +1,122 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.templete.vo;
+
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 优惠券模板分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponTempletePageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "优惠券类型 reward-满减 discount-折扣 random-随机")
+    private String type;
+
+    @ApiModelProperty(value = "优惠券名称")
+    private String name;
+
+    @ApiModelProperty(value = "名称备注")
+    private String couponNameRemark;
+
+    @ApiModelProperty(value = "优惠券图片")
+    private String image;
+
+    @ApiModelProperty(value = "发放数量")
+    private Integer count;
+
+    @ApiModelProperty(value = "已领取数量")
+    private Integer leadCount;
+
+    @ApiModelProperty(value = "已使用数量")
+    private Integer usedCount;
+
+    @ApiModelProperty(value = "适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用")
+    private Boolean goodsType;
+
+    @ApiModelProperty(value = "适用商品id")
+    private String productIds;
+
+    @ApiModelProperty(value = "使用门槛0-无门槛 1-有门槛")
+    private Boolean hasUseLimit;
+
+    @ApiModelProperty(value = "满多少元使用 0代表无限制")
+    private BigDecimal atLeast;
+
+    @ApiModelProperty(value = "发放面额 当type为reward时需要添加")
+    private BigDecimal money;
+
+    @ApiModelProperty(value = "1 =< 折扣 <= 9.9 当type为discount时需要添加")
+    private BigDecimal discount;
+
+    @ApiModelProperty(value = "最多折扣金额 当type为discount时可选择性添加")
+    private BigDecimal discountLimit;
+
+    @ApiModelProperty(value = "最低金额 当type为radom时需要添加")
+    private BigDecimal minMoney;
+
+    @ApiModelProperty(value = "最大金额 当type为radom时需要添加")
+    private BigDecimal maxMoney;
+
+    @ApiModelProperty(value = "过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期")
+    private Boolean validityType;
+
+    @ApiModelProperty(value = "使用开始日期 过期类型1时必填")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] startUseTime;
+
+    @ApiModelProperty(value = "使用结束日期 过期类型1时必填")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] endUseTime;
+
+    @ApiModelProperty(value = "当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效")
+    private Integer fixedTerm;
+
+    @ApiModelProperty(value = "是否无限制0-否 1是")
+    private Boolean whetherLimitless;
+
+    @ApiModelProperty(value = "每人最大领取个数")
+    private Integer maxFetch;
+
+    @ApiModelProperty(value = "是否开启过期提醒0-不开启 1-开启")
+    private Boolean whetherExpireNotice;
+
+    @ApiModelProperty(value = "过期前N天提醒")
+    private Integer expireNoticeFixedTerm;
+
+    @ApiModelProperty(value = "优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用")
+    private Boolean whetherForbidPreference;
+
+    @ApiModelProperty(value = "是否显示")
+    private Integer whetherShow;
+
+    @ApiModelProperty(value = "订单的优惠总金额")
+    private BigDecimal discountOrderMoney;
+
+    @ApiModelProperty(value = "用券总成交额")
+    private BigDecimal orderMoney;
+
+    @ApiModelProperty(value = "是否禁止发放0-否 1-是")
+    private Boolean whetherForbidden;
+
+    @ApiModelProperty(value = "使用优惠券购买的商品数量")
+    private Integer orderGoodsNum;
+
+    @ApiModelProperty(value = "状态(1进行中2已结束-1已关闭)")
+    private Integer status;
+
+    @ApiModelProperty(value = "有效日期结束时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] endTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] createTime;
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.templete.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 优惠券模板 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponTempleteRespVO extends CouponTempleteBaseVO {
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 17 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/controller/admin/templete/vo/CouponTempleteUpdateReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.coupon.controller.admin.templete.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 优惠券模板更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponTempleteUpdateReqVO extends CouponTempleteBaseVO {
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    @NotNull(message = "用户ID不能为空")
+    private Long id;
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/convert/CouponTemplete/CouponTempleteConvert.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.coupon.convert.CouponTemplete;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import cn.iocoder.yudao.module.coupon.controller.admin.templete.vo.CouponTempleteCreateReqVO;
+import cn.iocoder.yudao.module.coupon.controller.admin.templete.vo.CouponTempleteExcelVO;
+import cn.iocoder.yudao.module.coupon.controller.admin.templete.vo.CouponTempleteRespVO;
+import cn.iocoder.yudao.module.coupon.controller.admin.templete.vo.CouponTempleteUpdateReqVO;
+import cn.iocoder.yudao.module.coupon.dal.dataobject.CouponTemplete.CouponTempleteDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+
+/**
+ * 优惠券模板 Convert
+ *
+ * @author wxr
+ */
+@Mapper
+public interface CouponTempleteConvert {
+
+    CouponTempleteConvert INSTANCE = Mappers.getMapper(CouponTempleteConvert.class);
+
+    CouponTempleteDO convert(CouponTempleteCreateReqVO bean);
+
+    CouponTempleteDO convert(CouponTempleteUpdateReqVO bean);
+
+    CouponTempleteRespVO convert(CouponTempleteDO bean);
+
+    List<CouponTempleteRespVO> convertList(List<CouponTempleteDO> list);
+
+    PageResult<CouponTempleteRespVO> convertPage(PageResult<CouponTempleteDO> page);
+
+    List<CouponTempleteExcelVO> convertList02(List<CouponTempleteDO> list);
+
+}

+ 34 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/convert/coupon/CouponConvert.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.coupon.convert.coupon;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import cn.iocoder.yudao.module.coupon.controller.admin.coupon.vo.*;
+import cn.iocoder.yudao.module.coupon.dal.dataobject.coupon.CouponDO;
+
+/**
+ * 优惠券 Convert
+ *
+ * @author wxr
+ */
+@Mapper
+public interface CouponConvert {
+
+    CouponConvert INSTANCE = Mappers.getMapper(CouponConvert.class);
+
+    CouponDO convert(CouponCreateReqVO bean);
+
+    CouponDO convert(CouponUpdateReqVO bean);
+
+    CouponRespVO convert(CouponDO bean);
+
+    List<CouponRespVO> convertList(List<CouponDO> list);
+
+    PageResult<CouponRespVO> convertPage(PageResult<CouponDO> page);
+
+    List<CouponExcelVO> convertList02(List<CouponDO> list);
+
+}

+ 158 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/dal/dataobject/CouponTemplete/CouponTempleteDO.java

@@ -0,0 +1,158 @@
+package cn.iocoder.yudao.module.coupon.dal.dataobject.CouponTemplete;
+
+import lombok.*;
+import java.util.*;
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 优惠券模板 DO
+ *
+ * @author wxr
+ */
+@TableName("coupon_templete")
+@KeySequence("coupon_templete_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CouponTempleteDO extends BaseDO {
+
+    /**
+     * 用户ID
+     */
+    @TableId
+    private Long id;
+    /**
+     * 优惠券类型 reward-满减 discount-折扣 random-随机
+     */
+    private String type;
+    /**
+     * 优惠券名称
+     */
+    private String name;
+    /**
+     * 名称备注
+     */
+    private String couponNameRemark;
+    /**
+     * 优惠券图片
+     */
+    private String image;
+    /**
+     * 发放数量
+     */
+    private Integer count;
+    /**
+     * 已领取数量
+     */
+    private Integer leadCount;
+    /**
+     * 已使用数量
+     */
+    private Integer usedCount;
+    /**
+     * 适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用
+     */
+    private Integer goodsType;
+    /**
+     * 适用商品id
+     */
+    private String productIds;
+    /**
+     * 使用门槛0-无门槛 1-有门槛
+     */
+    private Boolean hasUseLimit;
+    /**
+     * 满多少元使用 0代表无限制
+     */
+    private BigDecimal atLeast;
+    /**
+     * 发放面额 当type为reward时需要添加
+     */
+    private BigDecimal money;
+    /**
+     * 1 =< 折扣 <= 9.9 当type为discount时需要添加
+     */
+    private BigDecimal discount;
+    /**
+     * 最多折扣金额 当type为discount时可选择性添加
+     */
+    private BigDecimal discountLimit;
+    /**
+     * 最低金额 当type为radom时需要添加
+     */
+    private BigDecimal minMoney;
+    /**
+     * 最大金额 当type为radom时需要添加
+     */
+    private BigDecimal maxMoney;
+    /**
+     * 过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期
+     */
+    private Integer validityType;
+    /**
+     * 使用开始日期 过期类型1时必填
+     */
+    private Date startUseTime;
+    /**
+     * 使用结束日期 过期类型1时必填
+     */
+    private Date endUseTime;
+    /**
+     * 当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效
+     */
+    private Integer fixedTerm;
+    /**
+     * 是否无限制0-否 1是
+     */
+    private Boolean whetherLimitless;
+    /**
+     * 每人最大领取个数
+     */
+    private Integer maxFetch;
+    /**
+     * 是否开启过期提醒0-不开启 1-开启
+     */
+    private Boolean whetherExpireNotice;
+    /**
+     * 过期前N天提醒
+     */
+    private Integer expireNoticeFixedTerm;
+    /**
+     * 优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用
+     */
+    private Boolean whetherForbidPreference;
+    /**
+     * 是否显示
+     */
+    private Integer whetherShow;
+    /**
+     * 订单的优惠总金额
+     */
+    private BigDecimal discountOrderMoney;
+    /**
+     * 用券总成交额
+     */
+    private BigDecimal orderMoney;
+    /**
+     * 是否禁止发放0-否 1-是
+     */
+    private Boolean whetherForbidden;
+    /**
+     * 使用优惠券购买的商品数量
+     */
+    private Integer orderGoodsNum;
+    /**
+     * 状态(1进行中2已结束-1已关闭)
+     */
+    private Integer status;
+    /**
+     * 有效日期结束时间
+     */
+    private Date endTime;
+
+}

+ 118 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/dal/dataobject/coupon/CouponDO.java

@@ -0,0 +1,118 @@
+package cn.iocoder.yudao.module.coupon.dal.dataobject.coupon;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import lombok.*;
+import java.util.*;
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.*;
+
+/**
+ * 优惠券 DO
+ *
+ * @author wxr
+ */
+@TableName("coupon")
+@KeySequence("coupon_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CouponDO extends BaseDO {
+
+    /**
+     * 用户ID
+     */
+    @TableId
+    private Long id;
+    /**
+     * 优惠券类型 reward-满减 discount-折扣 random-随机
+     */
+    private String type;
+    /**
+     * 优惠券名称
+     */
+    private String name;
+    /**
+     * 优惠券类型id
+     */
+    private Long couponTypeId;
+    /**
+     * 优惠券编码
+     */
+    private String couponCode;
+    /**
+     * 领用人
+     */
+    private Long memberId;
+    /**
+     * 优惠券使用订单id
+     */
+    private Long useOrderId;
+    /**
+     * 适用商品类型1-全部商品可用;2-指定商品可用;3-指定商品不可用
+     */
+    private Boolean goodsType;
+    /**
+     * 适用商品id
+     */
+    private String goodsIds;
+    /**
+     * 最小金额
+     */
+    private BigDecimal atLeast;
+    /**
+     * 面额
+     */
+    private BigDecimal money;
+    /**
+     * 1 =< 折扣 <= 9.9 当type为discount时需要添加
+     */
+    private BigDecimal discount;
+    /**
+     * 最多折扣金额 当type为discount时可选择性添加
+     */
+    private BigDecimal discountLimit;
+    /**
+     * 优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用
+     */
+    private Boolean whetherForbidPreference;
+    /**
+     * 是否开启过期提醒0-不开启 1-开启
+     */
+    private Boolean whetherExpireNotice;
+    /**
+     * 过期前N天提醒
+     */
+    private Integer expireNoticeFixedTerm;
+    /**
+     * 是否已提醒
+     */
+    private Boolean whetherNoticed;
+    /**
+     * 优惠券状态 1已领用(未使用) 2已使用 3已过期
+     */
+    private Integer state;
+    /**
+     * 获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取
+     */
+    private Boolean getType;
+    /**
+     * 领取时间
+     */
+    private Date fetchTime;
+    /**
+     * 使用时间
+     */
+    private Date useTime;
+    /**
+     * 可使用的开始时间
+     */
+    private Date startTime;
+    /**
+     * 有效期结束时间
+     */
+    private Date endTime;
+
+}

+ 98 - 0
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/dal/mysql/CouponTemplete/CouponTempleteMapper.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.coupon.dal.mysql.CouponTemplete;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.coupon.controller.admin.templete.vo.CouponTempleteExportReqVO;
+import cn.iocoder.yudao.module.coupon.controller.admin.templete.vo.CouponTempletePageReqVO;
+import cn.iocoder.yudao.module.coupon.dal.dataobject.CouponTemplete.CouponTempleteDO;
+import org.apache.ibatis.annotations.Mapper;
+
+
+/**
+ * 优惠券模板 Mapper
+ *
+ * @author wxr
+ */
+@Mapper
+public interface CouponTempleteMapper extends BaseMapperX<CouponTempleteDO> {
+
+    default PageResult<CouponTempleteDO> selectPage(CouponTempletePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<CouponTempleteDO>()
+                .eqIfPresent(CouponTempleteDO::getType, reqVO.getType())
+                .likeIfPresent(CouponTempleteDO::getName, reqVO.getName())
+                .eqIfPresent(CouponTempleteDO::getCouponNameRemark, reqVO.getCouponNameRemark())
+                .eqIfPresent(CouponTempleteDO::getImage, reqVO.getImage())
+                .eqIfPresent(CouponTempleteDO::getCount, reqVO.getCount())
+                .eqIfPresent(CouponTempleteDO::getLeadCount, reqVO.getLeadCount())
+                .eqIfPresent(CouponTempleteDO::getUsedCount, reqVO.getUsedCount())
+                .eqIfPresent(CouponTempleteDO::getGoodsType, reqVO.getGoodsType())
+                .eqIfPresent(CouponTempleteDO::getProductIds, reqVO.getProductIds())
+                .eqIfPresent(CouponTempleteDO::getHasUseLimit, reqVO.getHasUseLimit())
+                .eqIfPresent(CouponTempleteDO::getAtLeast, reqVO.getAtLeast())
+                .eqIfPresent(CouponTempleteDO::getMoney, reqVO.getMoney())
+                .eqIfPresent(CouponTempleteDO::getDiscount, reqVO.getDiscount())
+                .eqIfPresent(CouponTempleteDO::getDiscountLimit, reqVO.getDiscountLimit())
+                .eqIfPresent(CouponTempleteDO::getMinMoney, reqVO.getMinMoney())
+                .eqIfPresent(CouponTempleteDO::getMaxMoney, reqVO.getMaxMoney())
+                .eqIfPresent(CouponTempleteDO::getValidityType, reqVO.getValidityType())
+                .betweenIfPresent(CouponTempleteDO::getStartUseTime, reqVO.getStartUseTime())
+                .betweenIfPresent(CouponTempleteDO::getEndUseTime, reqVO.getEndUseTime())
+                .eqIfPresent(CouponTempleteDO::getFixedTerm, reqVO.getFixedTerm())
+                .eqIfPresent(CouponTempleteDO::getWhetherLimitless, reqVO.getWhetherLimitless())
+                .eqIfPresent(CouponTempleteDO::getMaxFetch, reqVO.getMaxFetch())
+                .eqIfPresent(CouponTempleteDO::getWhetherExpireNotice, reqVO.getWhetherExpireNotice())
+                .eqIfPresent(CouponTempleteDO::getExpireNoticeFixedTerm, reqVO.getExpireNoticeFixedTerm())
+                .eqIfPresent(CouponTempleteDO::getWhetherForbidPreference, reqVO.getWhetherForbidPreference())
+                .eqIfPresent(CouponTempleteDO::getWhetherShow, reqVO.getWhetherShow())
+                .eqIfPresent(CouponTempleteDO::getDiscountOrderMoney, reqVO.getDiscountOrderMoney())
+                .eqIfPresent(CouponTempleteDO::getOrderMoney, reqVO.getOrderMoney())
+                .eqIfPresent(CouponTempleteDO::getWhetherForbidden, reqVO.getWhetherForbidden())
+                .eqIfPresent(CouponTempleteDO::getOrderGoodsNum, reqVO.getOrderGoodsNum())
+                .eqIfPresent(CouponTempleteDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(CouponTempleteDO::getEndTime, reqVO.getEndTime())
+                .betweenIfPresent(CouponTempleteDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(CouponTempleteDO::getId));
+    }
+
+    default List<CouponTempleteDO> selectList(CouponTempleteExportReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<CouponTempleteDO>()
+                .eqIfPresent(CouponTempleteDO::getType, reqVO.getType())
+                .likeIfPresent(CouponTempleteDO::getName, reqVO.getName())
+                .eqIfPresent(CouponTempleteDO::getCouponNameRemark, reqVO.getCouponNameRemark())
+                .eqIfPresent(CouponTempleteDO::getImage, reqVO.getImage())
+                .eqIfPresent(CouponTempleteDO::getCount, reqVO.getCount())
+                .eqIfPresent(CouponTempleteDO::getLeadCount, reqVO.getLeadCount())
+                .eqIfPresent(CouponTempleteDO::getUsedCount, reqVO.getUsedCount())
+                .eqIfPresent(CouponTempleteDO::getGoodsType, reqVO.getGoodsType())
+                .eqIfPresent(CouponTempleteDO::getProductIds, reqVO.getProductIds())
+                .eqIfPresent(CouponTempleteDO::getHasUseLimit, reqVO.getHasUseLimit())
+                .eqIfPresent(CouponTempleteDO::getAtLeast, reqVO.getAtLeast())
+                .eqIfPresent(CouponTempleteDO::getMoney, reqVO.getMoney())
+                .eqIfPresent(CouponTempleteDO::getDiscount, reqVO.getDiscount())
+                .eqIfPresent(CouponTempleteDO::getDiscountLimit, reqVO.getDiscountLimit())
+                .eqIfPresent(CouponTempleteDO::getMinMoney, reqVO.getMinMoney())
+                .eqIfPresent(CouponTempleteDO::getMaxMoney, reqVO.getMaxMoney())
+                .eqIfPresent(CouponTempleteDO::getValidityType, reqVO.getValidityType())
+                .betweenIfPresent(CouponTempleteDO::getStartUseTime, reqVO.getStartUseTime())
+                .betweenIfPresent(CouponTempleteDO::getEndUseTime, reqVO.getEndUseTime())
+                .eqIfPresent(CouponTempleteDO::getFixedTerm, reqVO.getFixedTerm())
+                .eqIfPresent(CouponTempleteDO::getWhetherLimitless, reqVO.getWhetherLimitless())
+                .eqIfPresent(CouponTempleteDO::getMaxFetch, reqVO.getMaxFetch())
+                .eqIfPresent(CouponTempleteDO::getWhetherExpireNotice, reqVO.getWhetherExpireNotice())
+                .eqIfPresent(CouponTempleteDO::getExpireNoticeFixedTerm, reqVO.getExpireNoticeFixedTerm())
+                .eqIfPresent(CouponTempleteDO::getWhetherForbidPreference, reqVO.getWhetherForbidPreference())
+                .eqIfPresent(CouponTempleteDO::getWhetherShow, reqVO.getWhetherShow())
+                .eqIfPresent(CouponTempleteDO::getDiscountOrderMoney, reqVO.getDiscountOrderMoney())
+                .eqIfPresent(CouponTempleteDO::getOrderMoney, reqVO.getOrderMoney())
+                .eqIfPresent(CouponTempleteDO::getWhetherForbidden, reqVO.getWhetherForbidden())
+                .eqIfPresent(CouponTempleteDO::getOrderGoodsNum, reqVO.getOrderGoodsNum())
+                .eqIfPresent(CouponTempleteDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(CouponTempleteDO::getEndTime, reqVO.getEndTime())
+                .betweenIfPresent(CouponTempleteDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(CouponTempleteDO::getId));
+    }
+
+}

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio