Browse Source

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

# Conflicts:
#	sql/mysql/ruoyi-vue-pro.sql
#	yudao-ui-admin/src/views/system/user/index.vue
YunaiV 1 year ago
parent
commit
5122aee924
100 changed files with 3268 additions and 1124 deletions
  1. 11 11
      README.md
  2. 0 3
      sql/dm/README.md
  3. 5 5
      sql/dm/ruoyi-vue-pro-dm8.sql
  4. 0 0
      sql/mysql/mall.sql
  5. 81 38
      sql/mysql/pay_wallet.sql
  6. 245 78
      sql/mysql/ruoyi-vue-pro.sql
  7. 5 5
      sql/oracle/ruoyi-vue-pro.sql
  8. 5 5
      sql/postgresql/ruoyi-vue-pro.sql
  9. 5 5
      sql/sqlserver/ruoyi-vue-pro.sql
  10. 1 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java
  11. 7 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageParam.java
  12. 6 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  13. 30 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
  14. 37 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java
  15. 16 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java
  16. 3 3
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java
  17. 19 7
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
  18. 20 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
  19. 8 16
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
  20. 3 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java
  21. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java
  22. 5 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferTypeEnum.java
  23. 5 0
      yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
  24. 24 1
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  25. 19 3
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
  26. 10 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/CodegenController.java
  27. 17 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java
  28. 12 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableBaseVO.java
  29. 93 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/Demo01ContactController.java
  30. 28 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/vo/Demo01ContactPageReqVO.java
  31. 47 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/vo/Demo01ContactRespVO.java
  32. 36 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/vo/Demo01ContactSaveReqVO.java
  33. 90 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/Demo02CategoryController.java
  34. 25 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/vo/Demo02CategoryListReqVO.java
  35. 31 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/vo/Demo02CategoryRespVO.java
  36. 24 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/vo/Demo02CategorySaveReqVO.java
  37. 197 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/Demo03StudentController.java
  38. 1 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/package-info.java
  39. 30 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/vo/Demo03StudentPageReqVO.java
  40. 41 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/vo/Demo03StudentRespVO.java
  41. 39 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/vo/Demo03StudentSaveReqVO.java
  42. 8 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/package-info.java
  43. 0 19
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/TestDemoController.http
  44. 0 97
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/TestDemoController.java
  45. 0 32
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoBaseVO.java
  46. 0 11
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoCreateReqVO.java
  47. 0 38
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoExcelVO.java
  48. 0 33
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoExportReqVO.java
  49. 0 36
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoPageReqVO.java
  50. 0 19
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoRespVO.java
  51. 0 16
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoUpdateReqVO.java
  52. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/codegen/CodegenConvert.java
  53. 0 36
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/test/TestDemoConvert.java
  54. 39 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenTableDO.java
  55. 54 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo01/Demo01ContactDO.java
  56. 41 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo02/Demo02CategoryDO.java
  57. 43 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo03/Demo03CourseDO.java
  58. 43 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo03/Demo03GradeDO.java
  59. 50 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo03/Demo03StudentDO.java
  60. 0 50
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/test/TestDemoDO.java
  61. 5 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/codegen/CodegenTableMapper.java
  62. 26 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo01/Demo01ContactMapper.java
  63. 35 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo02/Demo02CategoryMapper.java
  64. 34 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo03/Demo03CourseMapper.java
  65. 32 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo03/Demo03GradeMapper.java
  66. 27 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo03/Demo03StudentMapper.java
  67. 0 45
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/test/TestDemoMapper.java
  68. 2 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenColumnHtmlTypeEnum.java
  69. 30 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenTemplateTypeEnum.java
  70. 8 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenService.java
  71. 39 6
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java
  72. 12 4
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java
  73. 223 36
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
  74. 55 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo01/Demo01ContactService.java
  75. 72 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo01/Demo01ContactServiceImpl.java
  76. 55 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo02/Demo02CategoryService.java
  77. 134 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo02/Demo02CategoryServiceImpl.java
  78. 158 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/Demo03StudentService.java
  79. 217 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/Demo03StudentServiceImpl.java
  80. 0 75
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/test/TestDemoService.java
  81. 0 91
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/test/TestDemoServiceImpl.java
  82. 155 33
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/controller.vm
  83. 0 13
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/_column.vm
  84. 0 39
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/baseVO.vm
  85. 0 30
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/createReqVO.vm
  86. 0 45
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/excelVO.vm
  87. 0 39
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/exportReqVO.vm
  88. 45 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/listReqVO.vm
  89. 9 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/pageReqVO.vm
  90. 35 6
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/respVO.vm
  91. 65 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm
  92. 0 30
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/updateReqVO.vm
  93. 0 34
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/convert/convert.vm
  94. 6 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/do.vm
  95. 49 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/do_sub.vm
  96. 19 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper.vm
  97. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper.xml.vm
  98. 51 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper_sub.vm
  99. 19 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/enums/errorcode.vm
  100. 89 12
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/service.vm

+ 11 - 11
README.md

@@ -27,10 +27,11 @@
 
 ![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
 
-* 管理后台的电脑端:Vue3 提供 [element-plus](https://gitee.com/yudaocode/yudao-ui-admin-vue3)、[vben(ant-design-vue)](https://gitee.com/yudaocode/yudao-ui-admin-vben) 两个版本,Vue2 提供 [element-ui](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin) 版本
-* 管理后台的移动端:采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
+* 管理后台的电脑端:Vue3 提供 `element-plus`、`vben(ant-design-vue)` 两个版本,Vue2 提供 `element-ui` 版本
+* 管理后台的移动端:采用 `uni-app` 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
 * 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
 * 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
+* 消息队列可使用 Event、Redis、RabbitMQ、Kafka、RocketMQ 等
 * 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
 * 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能
 * 支持 SaaS 多租户,可自定义每个租户的权限,提供透明化的多租户底层封装
@@ -48,7 +49,6 @@
 
 ### 后端项目
 
-
 | 项目                                                              | Star                                                                                                                                                                                                                                                                                             | 简介                          |
 |-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
 | [ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro)  | [![Gitee star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/ruoyi-vue-pro) [![GitHub stars](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Stars)](https://github.com/YunaiV/ruoyi-vue-pro)       | 基于 Spring Boot 多模块架构        |
@@ -57,14 +57,14 @@
 
 ### 前端项目
 
-| 项目                                                                                                       | Star                                                                                                                                                                                                                                                                                                                                                           | 简介                                     |
-|----------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
-| [yudao-ui-admin-vue3](https://gitee.com/yudaocode/yudao-ui-admin-vue3)                                   | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-vue3/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-vue3) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-vue3.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-vue3)                                               | 基于 Vue3 + element-plus 实现的管理后台         |
-| [yudao-ui-admin-vben](https://gitee.com/yudaocode/yudao-ui-admin-vben)                                   | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-vben/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-vben) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-vben.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-vben)                                               | 基于 Vue3 + vben(ant-design-vue) 实现的管理后台 |
-| [yudao-ui-admin](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin)               | [![Gitee star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin) [![GitHub stars](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Stars)](https://github.com/YunaiV/ruoyi-vue-pro/tree/master/yudao-ui-admin)               | 基于 Vue2 + element-ui 实现的管理后台           |
-| [yudao-ui-admin-uniapp](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) | [![Gitee star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) [![GitHub stars](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Stars)](https://github.com/YunaiV/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) | 基于 uni-app + uni-ui 实现的管理后台的小程序        |
-| [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view)                                         | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-go-view/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-go-view) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-go-view.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-go-view)                                                           | 基于 Vue3 + naive-ui 实现的大屏报表             |
-| [yudao-mall-uniapp](https://gitee.com/yudaocode/yudao-mall-uniapp)                   | [![Gitee star](https://gitee.com/yudaocode/yudao-mall-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-mall-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-mall-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-mall-uniapp)                   | 基于 uni-app 实现的商城小程序                    |
+| 项目                                                                         | Star                                                                                                                                                                                                                                                                                                                     | 简介                             |
+|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
+| [yudao-ui-admin-vue3](https://gitee.com/yudaocode/yudao-ui-admin-vue3)     | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-vue3/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-vue3) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-vue3.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-vue3)         | 基于 Vue3 + element-plus 实现的管理后台 |
+| [yudao-ui-admin-vben](https://gitee.com/yudaocode/yudao-ui-admin-vben)     | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-vben/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-vben) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-vben.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-vben)         | 基于 Vue3 + element-plus 实现的管理后台 |
+| [yudao-mall-uniapp](https://gitee.com/yudaocode/yudao-mall-uniapp)         | [![Gitee star](https://gitee.com/yudaocode/yudao-mall-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-mall-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-mall-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-mall-uniapp)                 | 基于 uni-app 实现的商城小程序            |
+| [yudao-ui-admin-vue2](https://gitee.com/yudaocode/yudao-ui-admin-vue2)     | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-vue2/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-vue2) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-vue2.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-vue2)         | 基于 Vue2 + element-ui 实现的管理后台   |
+| [yudao-ui-admin-uniapp](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-uniapp) | 基于 Vue2 + element-ui 实现的管理后台   |
+| [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view)           | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-go-view/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-go-view) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-go-view.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-go-view)                     | 基于 Vue3 + naive-ui 实现的大屏报表     |
 
 ## 🐰 分支说明
 

+ 0 - 3
sql/dm/README.md

@@ -1,3 +0,0 @@
-暂未适配国产 DM 数据库,如果你有需要,可以微信联系 wangwenbin-server 一起建设。
-
-你需要把表结构与数据导入到 DM 数据库,我来测试与适配代码。

+ 5 - 5
sql/dm/ruoyi-vue-pro-dm8.sql

@@ -3941,7 +3941,7 @@ CREATE TABLE "RUOYI_VUE_PRO"."SYSTEM_TENANT"
  "CONTACT_MOBILE" VARCHAR(500) NULL,
  "STATUS" TINYINT DEFAULT 0
  NOT NULL,
- "DOMAIN" VARCHAR(256) DEFAULT ''
+ "WEBSITE" VARCHAR(256) DEFAULT ''
  NULL,
  "PACKAGE_ID" BIGINT NOT NULL,
  "EXPIRE_TIME" TIMESTAMP(0) NOT NULL,
@@ -4943,9 +4943,9 @@ SET IDENTITY_INSERT "RUOYI_VUE_PRO"."SYSTEM_SOCIAL_USER" OFF;
 SET IDENTITY_INSERT "RUOYI_VUE_PRO"."SYSTEM_SOCIAL_USER_BIND" ON;
 SET IDENTITY_INSERT "RUOYI_VUE_PRO"."SYSTEM_SOCIAL_USER_BIND" OFF;
 SET IDENTITY_INSERT "RUOYI_VUE_PRO"."SYSTEM_TENANT" ON;
-INSERT INTO "RUOYI_VUE_PRO"."SYSTEM_TENANT"("ID","NAME","CONTACT_USER_ID","CONTACT_NAME","CONTACT_MOBILE","STATUS","DOMAIN","PACKAGE_ID","EXPIRE_TIME","ACCOUNT_COUNT","CREATOR","CREATE_TIME","UPDATER","UPDATE_TIME","DELETED") VALUES(1,'芋道源码',null,'芋艿','17321315478',0,'https://www.iocoder.cn',0,'2099-02-19 17:14:16',9999,'1','2021-01-05 17:03:47','1','2022-02-23 12:15:11',0);
-INSERT INTO "RUOYI_VUE_PRO"."SYSTEM_TENANT"("ID","NAME","CONTACT_USER_ID","CONTACT_NAME","CONTACT_MOBILE","STATUS","DOMAIN","PACKAGE_ID","EXPIRE_TIME","ACCOUNT_COUNT","CREATOR","CREATE_TIME","UPDATER","UPDATE_TIME","DELETED") VALUES(121,'小租户',110,'小王2','15601691300',0,'http://www.iocoder.cn',111,'2024-03-11 00:00:00',20,'1','2022-02-22 00:56:14','1','2022-05-17 10:03:59',0);
-INSERT INTO "RUOYI_VUE_PRO"."SYSTEM_TENANT"("ID","NAME","CONTACT_USER_ID","CONTACT_NAME","CONTACT_MOBILE","STATUS","DOMAIN","PACKAGE_ID","EXPIRE_TIME","ACCOUNT_COUNT","CREATOR","CREATE_TIME","UPDATER","UPDATE_TIME","DELETED") VALUES(122,'测试租户',113,'芋道','15601691300',0,'https://www.iocoder.cn',111,'2022-04-30 00:00:00',50,'1','2022-03-07 21:37:58','1','2022-03-07 21:37:58',0);
+INSERT INTO "RUOYI_VUE_PRO"."SYSTEM_TENANT"("ID","NAME","CONTACT_USER_ID","CONTACT_NAME","CONTACT_MOBILE","STATUS","WEBSITE","PACKAGE_ID","EXPIRE_TIME","ACCOUNT_COUNT","CREATOR","CREATE_TIME","UPDATER","UPDATE_TIME","DELETED") VALUES(1,'芋道源码',null,'芋艿','17321315478',0,'https://www.iocoder.cn',0,'2099-02-19 17:14:16',9999,'1','2021-01-05 17:03:47','1','2022-02-23 12:15:11',0);
+INSERT INTO "RUOYI_VUE_PRO"."SYSTEM_TENANT"("ID","NAME","CONTACT_USER_ID","CONTACT_NAME","CONTACT_MOBILE","STATUS","WEBSITE","PACKAGE_ID","EXPIRE_TIME","ACCOUNT_COUNT","CREATOR","CREATE_TIME","UPDATER","UPDATE_TIME","DELETED") VALUES(121,'小租户',110,'小王2','15601691300',0,'http://www.iocoder.cn',111,'2024-03-11 00:00:00',20,'1','2022-02-22 00:56:14','1','2022-05-17 10:03:59',0);
+INSERT INTO "RUOYI_VUE_PRO"."SYSTEM_TENANT"("ID","NAME","CONTACT_USER_ID","CONTACT_NAME","CONTACT_MOBILE","STATUS","WEBSITE","PACKAGE_ID","EXPIRE_TIME","ACCOUNT_COUNT","CREATOR","CREATE_TIME","UPDATER","UPDATE_TIME","DELETED") VALUES(122,'测试租户',113,'芋道','15601691300',0,'https://www.iocoder.cn',111,'2022-04-30 00:00:00',50,'1','2022-03-07 21:37:58','1','2022-03-07 21:37:58',0);
 
 SET IDENTITY_INSERT "RUOYI_VUE_PRO"."SYSTEM_TENANT" OFF;
 SET IDENTITY_INSERT "RUOYI_VUE_PRO"."SYSTEM_TENANT_PACKAGE" ON;
@@ -5674,7 +5674,7 @@ COMMENT ON COLUMN "RUOYI_VUE_PRO"."SYSTEM_TENANT"."CONTACT_MOBILE" IS '联系手
 
 COMMENT ON COLUMN "RUOYI_VUE_PRO"."SYSTEM_TENANT"."STATUS" IS '租户状态(0正常 1停用)';
 
-COMMENT ON COLUMN "RUOYI_VUE_PRO"."SYSTEM_TENANT"."DOMAIN" IS '绑定域名';
+COMMENT ON COLUMN "RUOYI_VUE_PRO"."SYSTEM_TENANT"."WEBSITE" IS '绑定域名';
 
 COMMENT ON COLUMN "RUOYI_VUE_PRO"."SYSTEM_TENANT"."PACKAGE_ID" IS '租户套餐编号';
 

+ 0 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/null/.gitkeep → sql/mysql/mall.sql


+ 81 - 38
sql/mysql/pay_wallet.sql

@@ -5,18 +5,26 @@ DROP TABLE IF EXISTS `pay_transfer`;
 CREATE TABLE `pay_transfer`
 (
     `id`                   bigint       NOT NULL AUTO_INCREMENT COMMENT '编号',
-    `type`                 int          NOT NULL COMMENT '类型',
+    `no`                   varchar(64)  NOT NULL COMMENT '转账单号',
     `app_id`               bigint       NOT NULL COMMENT '应用编号',
-    `merchant_order_id`    varchar(64)  NOT NULL COMMENT '商户订单编号',
-    `price`                int          NOT NULL COMMENT '转账金额,单位:分',
-    `subject`              varchar(512) NOT NULL COMMENT '转账标题',
-    `payee_info`           varchar(512) NOT NULL COMMENT '收款人信息,不同类型和渠道不同',
+    `channel_id`           bigint       NOT NULL  COMMENT '转账渠道编号',
+    `channel_code`         varchar(32)  NOT NULL  COMMENT '转账渠道编码',
+    `merchant_transfer_id` varchar(64)  NOT NULL COMMENT '商户转账单编号',
+    `type`                 int          NOT NULL COMMENT '类型',
     `status`               tinyint      NOT NULL COMMENT '转账状态',
     `success_time`         datetime     NULL COMMENT '转账成功时间',
-    `extension_id`         bigint       NULL  COMMENT '转账渠道编号',
-    `no`                   varchar(64)  NULL COMMENT '转账单号',
-    `channel_id`           bigint       NULL  COMMENT '转账渠道编号',
-    `channel_code`         varchar(32)  NULL  COMMENT '转账渠道编码',
+    `price`                int          NOT NULL COMMENT '转账金额,单位:分',
+    `subject`              varchar(512) NOT NULL COMMENT '转账标题',
+    `user_name`            varchar(64)  NULL COMMENT '收款人姓名',
+    `alipay_logon_id`      varchar(64)  NULL COMMENT '支付宝登录号',
+    `openid`               varchar(64)   NULL COMMENT '微信 openId',
+    `notify_url`           varchar(1024) NOT NULL COMMENT '异步通知商户地址',
+    `user_ip`              varchar(50)   NOT NULL COMMENT '用户 IP',
+    `channel_extras`       varchar(512) NULL DEFAULT NULL COMMENT '渠道的额外参数',
+    `channel_transfer_no`  varchar(64)  NULL DEFAULT NULL COMMENT '渠道转账单号',
+    `channel_error_code`   varchar(128) NULL DEFAULT NULL COMMENT '调用渠道的错误码',
+    `channel_error_msg`    varchar(256) NULL DEFAULT NULL COMMENT '调用渠道的错误提示',
+    `channel_notify_data`  varchar(4096) NULL DEFAULT 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 '更新者',
@@ -26,39 +34,18 @@ CREATE TABLE `pay_transfer`
     PRIMARY KEY (`id`) USING BTREE
 ) ENGINE=InnoDB COMMENT='转账单表';
 
--- ----------------------------
--- 转账扩展单
--- ----------------------------
-DROP TABLE IF EXISTS `pay_transfer_extension`;
-CREATE TABLE `pay_transfer_extension`
-(
-    `id`                   bigint        NOT NULL AUTO_INCREMENT COMMENT '编号',
-    `no`                   varchar(64)   NOT NULL COMMENT '转账单号',
-    `transfer_id`          bigint        NOT NULL COMMENT '转账单编号',
-    `channel_id`           bigint        NOT NULL COMMENT '转账渠道编号',
-    `channel_code`         varchar(32)   NOT NULL COMMENT '转账渠道编码',
-    `channel_extras`       varchar(512)  NULL DEFAULT NULL COMMENT '支付渠道的额外参数',
-    `status`               tinyint       NOT NULL COMMENT '转账状态',
-    `channel_notify_data`  varchar(4096) NULL DEFAULT NULL COMMENT '支付渠道异步通知的内容',
-    `creator`              varchar(64)   NULL DEFAULT '' COMMENT '创建者',
-    `create_time`          datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    `updater`              varchar(64)   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 COMMENT='转账拓展单表';
-
 -- ----------------------------
 -- Table structure for pay_demo_transfer
 -- ----------------------------
 DROP TABLE IF EXISTS `pay_demo_transfer`;
 CREATE TABLE `pay_demo_transfer`  (
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单编号',
-  `user_id` bigint UNSIGNED NOT NULL COMMENT '用户编号',
-  `price` int NOT NULL COMMENT '转账金额,单位:分',
+  `app_id`  bigint NOT NULL COMMENT '应用编号',
   `type`  int NOT NULL COMMENT '转账类型',
-  `payee_info` varchar(512) NOT NULL COMMENT '收款人信息,不同类型和渠道不同',
+  `price` int NOT NULL COMMENT '转账金额,单位:分',
+  `user_name`            varchar(64)  NULL COMMENT '收款人姓名',
+  `alipay_logon_id`      varchar(64)  NULL COMMENT '支付宝登录号',
+  `openid`               varchar(64)  NULL COMMENT '微信 openId',
   `transfer_status` tinyint      NOT NULL DEFAULT 0 COMMENT '转账状态',
   `pay_transfer_id` bigint NULL DEFAULT NULL COMMENT '转账订单编号',
   `pay_channel_code` varchar(16)  NULL DEFAULT NULL COMMENT '转账支付成功渠道',
@@ -70,11 +57,11 @@ CREATE TABLE `pay_demo_transfer`  (
   `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 = '示例业务转账订单\n';
+) ENGINE = InnoDB  COMMENT = '示例业务转账订单';
 
 
-ALTER TABLE `pay_channel`
-    MODIFY COLUMN `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '支付渠道配置' AFTER `app_id`;
+-- ALTER TABLE `pay_channel`
+--   MODIFY COLUMN `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '支付渠道配置' AFTER `app_id`;
 
 -- ----------------------------
 -- 充值套餐表
@@ -203,3 +190,59 @@ VALUES (
            '', '', '', 0
        );
 
+-- 支付实战和转账实战数据库脚本
+
+update  system_menu set deleted = 1  where id = 2161;
+
+INSERT INTO system_menu(
+    name, permission, type, sort, parent_id,
+    path, icon, component, status, component_name
+)
+VALUES (
+           '接入示例', '', 1, 99, 1117,
+           'demo', 'ep:caret-right', '', 0, ''
+       );
+
+SELECT @parentId1 := LAST_INSERT_ID();
+
+INSERT INTO system_menu(
+    name, permission, type, sort, parent_id,
+    path, icon, component, status, component_name
+)
+VALUES (
+           '支付实战', '', 2, 1, @parentId1,
+           'demo-order', 'fa:leaf', 'pay/demo/order/index', 0, NULL
+       );
+
+INSERT INTO system_menu(
+    name, permission, type, sort, parent_id,
+    path, icon, component, status, component_name
+)
+VALUES (
+           '转账实战', '', 2, 1, @parentId1,
+           'demo-transfer', 'fa:leaf', 'pay/demo/transfer/index', 0, NULL
+       );
+
+-- 转账状态和转账类型数据字典
+INSERT INTO `system_dict_type`(`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES ('支付转账类型', 'pay_transfer_type', 0, '', '1', '2023-10-28 16:27:18', '1', '2023-10-28 16:27:18', b'0', '1970-01-01 00:00:00');
+INSERT INTO `system_dict_type`(`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES ('转账订单状态', 'pay_transfer_status', 0, '', '1', '2023-10-28 16:18:32', '1', '2023-10-28 16:18:32', b'0', '1970-01-01 00:00:00');
+
+INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '钱包余额', '4', 'pay_transfer_type', 0, 'info', '', '', '1', '2023-10-28 16:28:37', '1', '2023-10-28 16:28:37', b'0');
+INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3, '银行卡', '3', 'pay_transfer_type', 0, 'default', '', '', '1', '2023-10-28 16:28:21', '1', '2023-10-28 16:28:21', b'0');
+INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, '微信余额', '2', 'pay_transfer_type', 0, 'info', '', '', '1', '2023-10-28 16:28:07', '1', '2023-10-28 16:28:07', b'0');
+INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '支付宝余额', '1', 'pay_transfer_type', 0, 'default', '', '', '1', '2023-10-28 16:27:44', '1', '2023-10-28 16:27:44', b'0');
+INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '转账失败', '30', 'pay_transfer_status', 0, 'warning', '', '', '1', '2023-10-28 16:24:16', '1', '2023-10-28 16:24:16', b'0');
+INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3, '转账成功', '20', 'pay_transfer_status', 0, 'success', '', '', '1', '2023-10-28 16:23:50', '1', '2023-10-28 16:23:50', b'0');
+INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, '转账进行中', '10', 'pay_transfer_status', 0, 'info', '', '', '1', '2023-10-28 16:23:12', '1', '2023-10-28 16:23:12', b'0');
+INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '等待转账', '0', 'pay_transfer_status', 0, 'default', '', '', '1', '2023-10-28 16:21:43', '1', '2023-10-28 16:23:22', b'0');
+
+-- 转账订单菜单脚本
+
+INSERT INTO system_menu(
+    name, permission, type, sort, parent_id,
+    path, icon, component, status, component_name
+)
+VALUES (
+           '转账订单', '', 2, 3, 1117,
+           'transfer', 'ep:credit-card', 'pay/transfer/index', 0, 'PayTransfer'
+       );

+ 245 - 78
sql/mysql/ruoyi-vue-pro.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80034
  File Encoding         : 65001
 
- Date: 29/10/2023 19:22:42
+ Date: 18/11/2023 17:48:18
 */
 
 SET NAMES utf8mb4;
@@ -385,7 +385,7 @@ CREATE TABLE `infra_api_error_log`  (
   `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 = 1750 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 1964 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
 
 -- ----------------------------
 -- Records of infra_api_error_log
@@ -423,7 +423,7 @@ CREATE TABLE `infra_codegen_column`  (
   `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 AUTO_INCREMENT = 1805 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 1905 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
 
 -- ----------------------------
 -- Records of infra_codegen_column
@@ -450,13 +450,18 @@ CREATE TABLE `infra_codegen_table`  (
   `template_type` tinyint NOT NULL DEFAULT 1 COMMENT '模板类型',
   `front_type` tinyint NOT NULL COMMENT '前端类型',
   `parent_menu_id` bigint NULL DEFAULT NULL COMMENT '父菜单编号',
+  `master_table_id` bigint NULL DEFAULT NULL COMMENT '主表的编号',
+  `sub_join_column_id` bigint NULL DEFAULT NULL COMMENT '子表关联主表的字段编号',
+  `sub_join_many` bit(1) NULL DEFAULT NULL COMMENT '主表与子表是否一对多',
+  `tree_parent_column_id` bigint NULL DEFAULT NULL COMMENT '树表的父字段编号',
+  `tree_name_column_id` bigint NULL DEFAULT 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 '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 137 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 146 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
 
 -- ----------------------------
 -- Records of infra_codegen_table
@@ -521,6 +526,150 @@ CREATE TABLE `infra_data_source_config`  (
 BEGIN;
 COMMIT;
 
+-- ----------------------------
+-- Table structure for infra_demo01_contact
+-- ----------------------------
+DROP TABLE IF EXISTS `infra_demo01_contact`;
+CREATE TABLE `infra_demo01_contact`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
+  `sex` tinyint(1) NOT NULL COMMENT '性别',
+  `birthday` datetime NOT NULL COMMENT '出生年',
+  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介',
+  `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 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 = '示例联系人表';
+
+-- ----------------------------
+-- Records of infra_demo01_contact
+-- ----------------------------
+BEGIN;
+INSERT INTO `infra_demo01_contact` (`id`, `name`, `sex`, `birthday`, `description`, `avatar`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '土豆', 2, '2023-11-07 00:00:00', '<p>天蚕土豆!呀</p>', 'http://127.0.0.1:48080/admin-api/infra/file/4/get/46f8fa1a37db3f3960d8910ff2fe3962ab3b2db87cf2f8ccb4dc8145b8bdf237.jpeg', '1', '2023-11-15 23:34:30', '1', '2023-11-15 23:47:39', b'0', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for infra_demo02_category
+-- ----------------------------
+DROP TABLE IF EXISTS `infra_demo02_category`;
+CREATE TABLE `infra_demo02_category`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
+  `parent_id` bigint 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 = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '示例分类表';
+
+-- ----------------------------
+-- Records of infra_demo02_category
+-- ----------------------------
+BEGIN;
+INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '土豆', 0, '1', '2023-11-15 23:34:30', '1', '2023-11-16 20:24:23', b'0', 1);
+INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '番茄', 0, '1', '2023-11-16 20:24:00', '1', '2023-11-16 20:24:15', b'0', 1);
+INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, '怪怪', 0, '1', '2023-11-16 20:24:32', '1', '2023-11-16 20:24:32', b'0', 1);
+INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, '小番茄', 2, '1', '2023-11-16 20:24:39', '1', '2023-11-16 20:24:39', b'0', 1);
+INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, '大番茄', 2, '1', '2023-11-16 20:24:46', '1', '2023-11-16 20:24:46', b'0', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for infra_demo03_course
+-- ----------------------------
+DROP TABLE IF EXISTS `infra_demo03_course`;
+CREATE TABLE `infra_demo03_course`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `student_id` bigint NOT NULL COMMENT '学生编号',
+  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
+  `score` 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 = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '学生课程表';
+
+-- ----------------------------
+-- Records of infra_demo03_course
+-- ----------------------------
+BEGIN;
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 2, '语文', 66, '1', '2023-11-16 23:21:49', '1', '2023-11-16 23:21:49', b'0', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 2, '数学', 22, '1', '2023-11-16 23:21:49', '1', '2023-11-16 23:21:49', b'0', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:44:40', b'1', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:44:40', b'1', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (8, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:47:09', b'1', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (9, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:47:09', b'1', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (10, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2023-11-16 23:47:10', b'0', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (11, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2023-11-16 23:47:10', b'0', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (12, 2, '电脑', 33, '1', '2023-11-17 00:20:42', '1', '2023-11-16 16:20:45', b'1', 1);
+INSERT INTO `infra_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (13, 9, '滑雪', 12, '1', '2023-11-17 13:13:20', '1', '2023-11-17 13:13:20', b'0', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for infra_demo03_grade
+-- ----------------------------
+DROP TABLE IF EXISTS `infra_demo03_grade`;
+CREATE TABLE `infra_demo03_grade`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `student_id` bigint NOT NULL COMMENT '学生编号',
+  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
+  `teacher` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci 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 = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '学生班级表';
+
+-- ----------------------------
+-- Records of infra_demo03_grade
+-- ----------------------------
+BEGIN;
+INSERT INTO `infra_demo03_grade` (`id`, `student_id`, `name`, `teacher`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 2, '三年 2 班', '周杰伦', '1', '2023-11-16 23:21:49', '1', '2023-11-16 23:21:49', b'0', 1);
+INSERT INTO `infra_demo03_grade` (`id`, `student_id`, `name`, `teacher`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (8, 5, '华为', '遥遥领先', '1', '2023-11-16 23:22:46', '1', '2023-11-16 23:47:10', b'0', 1);
+INSERT INTO `infra_demo03_grade` (`id`, `student_id`, `name`, `teacher`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (9, 9, '小图', '小娃111', '1', '2023-11-17 13:10:23', '1', '2023-11-17 13:10:23', b'0', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for infra_demo03_student
+-- ----------------------------
+DROP TABLE IF EXISTS `infra_demo03_student`;
+CREATE TABLE `infra_demo03_student`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
+  `sex` tinyint NOT NULL COMMENT '性别',
+  `birthday` datetime NOT NULL COMMENT '出生日期',
+  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci 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 = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '学生表';
+
+-- ----------------------------
+-- Records of infra_demo03_student
+-- ----------------------------
+BEGIN;
+INSERT INTO `infra_demo03_student` (`id`, `name`, `sex`, `birthday`, `description`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '小白', 1, '2023-11-16 00:00:00', '<p>厉害</p>', '1', '2023-11-16 23:21:49', '1', '2023-11-17 16:49:06', b'0', 1);
+INSERT INTO `infra_demo03_student` (`id`, `name`, `sex`, `birthday`, `description`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, '大黑', 2, '2023-11-13 00:00:00', '<p>你在教我做事?</p>', '1', '2023-11-16 23:22:46', '1', '2023-11-17 16:49:07', b'0', 1);
+INSERT INTO `infra_demo03_student` (`id`, `name`, `sex`, `birthday`, `description`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (9, '小花', 1, '2023-11-07 00:00:00', '<p>哈哈哈</p>', '1', '2023-11-17 00:04:47', '1', '2023-11-17 16:49:08', b'0', 1);
+COMMIT;
+
 -- ----------------------------
 -- Table structure for infra_file
 -- ----------------------------
@@ -539,7 +688,7 @@ CREATE TABLE `infra_file`  (
   `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 AUTO_INCREMENT = 1109 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1128 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
 
 -- ----------------------------
 -- Records of infra_file
@@ -588,7 +737,7 @@ CREATE TABLE `infra_file_content`  (
   `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 AUTO_INCREMENT = 202 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 221 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
 
 -- ----------------------------
 -- Records of infra_file_content
@@ -616,7 +765,7 @@ CREATE TABLE `infra_job`  (
   `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 AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
+) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
 
 -- ----------------------------
 -- Records of infra_job
@@ -657,7 +806,7 @@ CREATE TABLE `infra_job_log`  (
   `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 AUTO_INCREMENT = 233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 232 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
 
 -- ----------------------------
 -- Records of infra_job_log
@@ -665,31 +814,6 @@ CREATE TABLE `infra_job_log`  (
 BEGIN;
 COMMIT;
 
--- ----------------------------
--- Table structure for infra_test_demo
--- ----------------------------
-DROP TABLE IF EXISTS `infra_test_demo`;
-CREATE TABLE `infra_test_demo`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
-  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
-  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态',
-  `type` tinyint NOT NULL COMMENT '类型',
-  `category` tinyint NOT NULL COMMENT '分类',
-  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 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 '是否删除',
-  PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
-
--- ----------------------------
--- Records of infra_test_demo
--- ----------------------------
-BEGIN;
-COMMIT;
-
 -- ----------------------------
 -- Table structure for member_address
 -- ----------------------------
@@ -710,7 +834,7 @@ CREATE TABLE `member_address`  (
   `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 = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
+) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
 
 -- ----------------------------
 -- Records of member_address
@@ -769,7 +893,7 @@ CREATE TABLE `member_experience_record`  (
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员经验记录-用户编号',
   INDEX `idx_user_biz_type`(`user_id` ASC, `biz_type` ASC) USING BTREE COMMENT '会员经验记录-用户业务类型'
-) ENGINE = InnoDB AUTO_INCREMENT = 42 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员经验记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员经验记录';
 
 -- ----------------------------
 -- Records of member_experience_record
@@ -827,7 +951,7 @@ CREATE TABLE `member_group`  (
   `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 = '用户分组';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户分组';
 
 -- ----------------------------
 -- Records of member_group
@@ -855,7 +979,7 @@ CREATE TABLE `member_level`  (
   `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 = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级';
+) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级';
 
 -- ----------------------------
 -- Records of member_level
@@ -885,7 +1009,7 @@ CREATE TABLE `member_level_record`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员等级记录-用户编号'
-) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
 
 -- ----------------------------
 -- Records of member_level_record
@@ -915,7 +1039,7 @@ CREATE TABLE `member_point_record`  (
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `index_userId`(`user_id` ASC) USING BTREE,
   INDEX `index_title`(`title` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 61 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 60 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
 
 -- ----------------------------
 -- Records of member_point_record
@@ -1055,7 +1179,7 @@ CREATE TABLE `member_tag`  (
   `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 = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员标签';
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员标签';
 
 -- ----------------------------
 -- Records of member_tag
@@ -1131,7 +1255,7 @@ CREATE TABLE `system_dept`  (
 -- Records of system_dept
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, '芋道源码', 0, 0, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-06-19 00:29:10', b'0', 1);
+INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, '芋道源码', 0, 0, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2023-11-14 23:30:36', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (101, '深圳总公司', 100, 1, 104, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-05-16 20:25:23', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (102, '长沙分公司', 100, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:40', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, '研发部门', 101, 1, 104, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '103', '2022-01-14 01:04:14', b'0', 1);
@@ -1165,14 +1289,14 @@ CREATE TABLE `system_dict_data`  (
   `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 AUTO_INCREMENT = 1398 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1447 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
 
 -- ----------------------------
 -- Records of system_dict_data
 -- ----------------------------
 BEGIN;
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 1, '男', '1', 'system_user_sex', 0, 'default', 'A', '性别男', 'admin', '2021-01-05 17:03:48', '1', '2022-03-29 00:14:39', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 2, '女', '2', 'system_user_sex', 1, 'success', '', '性别女', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 01:30:51', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 2, '女', '2', 'system_user_sex', 0, 'success', '', '性别女', 'admin', '2021-01-05 17:03:48', '1', '2023-11-15 23:30:37', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8, 1, '正常', '1', 'infra_job_status', 0, 'success', '', '正常状态', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 19:33:38', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (9, 2, '暂停', '2', 'infra_job_status', 0, 'danger', '', '停用状态', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 19:33:45', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (12, 1, '系统内置', '1', 'infra_config_type', 0, 'danger', '', '参数类型 - 系统内置', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 19:06:02', b'0');
@@ -1468,6 +1592,18 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1432, 8, '陌拜', '8', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:04', '1', '2023-10-28 23:10:04', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1433, 9, '电话咨询', '9', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:18', '1', '2023-10-28 23:10:18', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1434, 10, '邮件咨询', '10', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:33', '1', '2023-10-28 23:10:33', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1435, 10, 'Gitee', '10', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:04:42', '1', '2023-11-04 13:04:42', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1436, 20, '钉钉', '20', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:04:54', '1', '2023-11-04 13:04:54', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1437, 30, '企业微信', '30', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:09', '1', '2023-11-04 13:05:09', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1438, 31, '微信公众平台', '31', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:18', '1', '2023-11-04 13:05:18', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1439, 32, '微信开放平台', '32', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:30', '1', '2023-11-04 13:05:30', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1440, 34, '微信小程序', '34', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1441, 1, '上架', '1', 'crm_product_status', 0, 'success', '', '', '1', '2023-10-30 21:49:34', '1', '2023-10-30 21:49:34', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1442, 0, '下架', '0', 'crm_product_status', 0, 'success', '', '', '1', '2023-10-30 21:49:13', '1', '2023-10-30 21:49:13', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1443, 15, '子表', '15', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-13 23:06:16', '1', '2023-11-13 23:06:16', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1444, 10, '主表(标准模式)', '10', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-14 12:32:49', '1', '2023-11-14 12:32:49', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1445, 11, '主表(ERP 模式)', '11', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-14 12:33:05', '1', '2023-11-14 12:33:05', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1446, 12, '主表(内嵌模式)', '12', 'infra_codegen_template_type', 0, '', '', '', '1', '2023-11-14 12:33:31', '1', '2023-11-14 12:33:31', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -1488,7 +1624,7 @@ CREATE TABLE `system_dict_type`  (
   `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
   PRIMARY KEY (`id`) USING BTREE,
   UNIQUE INDEX `dict_type`(`type` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 601 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
+) ENGINE = InnoDB AUTO_INCREMENT = 605 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
 
 -- ----------------------------
 -- Records of system_dict_type
@@ -1565,7 +1701,9 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (186, '客户所属行业', 'crm_customer_industry', 0, 'CRM 客户所属行业', '1', '2023-10-28 22:57:07', '1', '2023-10-28 15:11:16', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (187, '客户等级', 'crm_customer_level', 0, 'CRM 客户等级', '1', '2023-10-28 22:59:12', '1', '2023-10-28 15:11:16', b'0', NULL);
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (188, '客户来源', 'crm_customer_source', 0, 'CRM 客户来源', '1', '2023-10-28 23:00:34', '1', '2023-10-28 15:11:16', b'0', NULL);
-INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (600, 'Banner Position', 'promotion_banner_position', 0, '', '1', '2023-10-08 07:24:25', '1', '2023-10-08 07:24:25', b'0', '1970-01-01 00:00:00');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (600, 'Banner 位置', 'promotion_banner_position', 0, '', '1', '2023-10-08 07:24:25', '1', '2023-11-04 13:04:02', b'0', '1970-01-01 00:00:00');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (601, '社交类型', 'system_social_type', 0, '', '1', '2023-11-04 13:03:54', '1', '2023-11-04 13:03:54', b'0', '1970-01-01 00:00:00');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (604, '产品状态', 'crm_product_status', 0, '', '1', '2023-10-30 21:47:59', '1', '2023-10-30 21:48:45', b'0', '1970-01-01 00:00:00');
 COMMIT;
 
 -- ----------------------------
@@ -1585,7 +1723,7 @@ CREATE TABLE `system_error_code`  (
   `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 AUTO_INCREMENT = 5933 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
+) ENGINE = InnoDB AUTO_INCREMENT = 5932 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
 
 -- ----------------------------
 -- Records of system_error_code
@@ -1614,7 +1752,7 @@ CREATE TABLE `system_login_log`  (
   `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 = 2626 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 2647 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
 
 -- ----------------------------
 -- Records of system_login_log
@@ -1744,7 +1882,7 @@ CREATE TABLE `system_menu`  (
   `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 AUTO_INCREMENT = 2391 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
+) ENGINE = InnoDB AUTO_INCREMENT = 2504 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
 
 -- ----------------------------
 -- Records of system_menu
@@ -1832,11 +1970,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1065, '设置用户角色', 'system:permission:assign-user-role', 3, 8, 101, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-01-07 10:23:28', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1066, '获得 Redis 监控信息', 'infra:redis:get-monitor-info', 3, 1, 113, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-01-26 01:02:31', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1067, '获得 Redis Key 列表', 'infra:redis:get-key-list', 3, 2, 113, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-01-26 01:02:52', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1070, '代码生成示例', 'infra:test-demo:query', 2, 1, 2, 'test-demo', 'validCode', 'infra/testDemo/index', NULL, 0, b'1', b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1071, '测试示例表创建', 'infra:test-demo:create', 3, 1, 1070, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1072, '测试示例表更新', 'infra:test-demo:update', 3, 2, 1070, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1073, '测试示例表删除', 'infra:test-demo:delete', 3, 3, 1070, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1074, '测试示例表导出', 'infra:test-demo:export', 3, 4, 1070, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-02-06 12:42:49', '1', '2022-04-20 17:03:10', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1070, '代码生成案例', '', 1, 1, 2, 'demo', 'ep:aim', 'infra/testDemo/index', NULL, 0, b'1', b'1', b'1', '', '2021-02-06 12:42:49', '1', '2023-11-15 23:45:53', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1075, '任务触发', 'infra:job:trigger', 3, 8, 110, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-02-07 13:03:10', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1076, '数据库文档', '', 2, 4, 2, 'db-doc', 'table', 'infra/dbDoc/index', 'InfraDBDoc', 0, b'1', b'1', b'1', '', '2021-02-08 01:41:47', '1', '2023-04-08 09:13:38', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1077, '监控平台', '', 2, 13, 2, 'skywalking', 'eye-open', 'infra/skywalking/index', 'InfraSkyWalking', 0, b'1', b'1', b'1', '', '2021-02-08 20:41:31', '1', '2023-04-08 10:39:06', b'0');
@@ -2283,6 +2417,46 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2431, '回款计划更新', 'crm:receivable-plan:update', 3, 3, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2432, '回款计划删除', 'crm:receivable-plan:delete', 3, 4, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2433, '回款计划导出', 'crm:receivable-plan:export', 3, 5, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2434, '核销订单', '', 2, 2, 2166, 'pick-up-order', 'ep:list', 'mall/trade/delivery/pickUpOrder/index', 'PickUpOrder', 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2435, '商城装修', '', 2, 20, 2030, 'diy-template', 'fa6-solid:brush', 'mall/promotion/diy/template/index', 'DiyTemplate', 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2436, '装修模板', '', 2, 1, 2435, 'diy-template', 'fa6-solid:brush', 'mall/promotion/diy/template/index', 'DiyTemplate', 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2437, '装修模板查询', 'promotion:diy-template:query', 3, 1, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2438, '装修模板创建', 'promotion:diy-template:create', 3, 2, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2439, '装修模板更新', 'promotion:diy-template:update', 3, 3, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2440, '装修模板删除', 'promotion:diy-template:delete', 3, 4, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2441, '装修模板使用', 'promotion:diy-template:use', 3, 5, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2442, '装修页面', '', 2, 2, 2435, 'diy-page', 'foundation:page-edit', 'mall/promotion/diy/page/index', 'DiyPage', 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2443, '装修页面查询', 'promotion:diy-page:query', 3, 1, 2442, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2444, '装修页面创建', 'promotion:diy-page:create', 3, 2, 2442, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2445, '装修页面更新', 'promotion:diy-page:update', 3, 3, 2442, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2446, '装修页面删除', 'promotion:diy-page:delete', 3, 4, 2442, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2447, '三方登录', '', 1, 10, 1, 'social', 'fa:500px', '', '', 0, b'1', b'1', b'1', '1', '2023-11-04 12:12:01', '1', '2023-11-04 13:06:45', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2448, '三方应用', '', 2, 1, 2447, 'client', 'ep:set-up', 'views/system/social/client/index.vue', 'SocialClient', 0, b'1', b'1', b'1', '1', '2023-11-04 12:17:19', '1', '2023-11-04 12:17:19', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2449, '三方应用查询', 'system:social-client:query', 3, 1, 2448, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-11-04 12:43:12', '1', '2023-11-04 12:43:33', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2450, '三方应用创建', 'system:social-client:create', 3, 2, 2448, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-11-04 12:43:58', '1', '2023-11-04 12:43:58', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2451, '三方应用更新', 'system:social-client:update', 3, 3, 2448, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-11-04 12:44:27', '1', '2023-11-04 12:44:27', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2452, '三方应用删除', 'system:social-client:delete', 3, 4, 2448, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-11-04 12:44:43', '1', '2023-11-04 12:44:43', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2453, '三方用户', 'system:social-user:query', 2, 2, 2447, 'user', 'ep:avatar', 'system/social/user/index.vue', 'SocialUser', 0, b'1', b'1', b'1', '1', '2023-11-04 14:01:05', '1', '2023-11-04 14:01:05', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2472, '主子表(内嵌)', '', 2, 12, 1070, 'demo03-inner', 'fa:power-off', 'infra/demo/demo03/inner/index', 'Demo03StudentInner', 0, b'1', b'1', b'1', '', '2023-11-13 04:39:51', '1', '2023-11-16 23:53:46', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2478, '单表(增删改查)', '', 2, 1, 1070, 'demo01-contact', 'ep:bicycle', 'infra/demo/demo01/index', 'Demo01Contact', 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '1', '2023-11-16 20:34:40', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2479, '示例联系人查询', 'infra:demo01-contact:query', 3, 1, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2480, '示例联系人创建', 'infra:demo01-contact:create', 3, 2, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2481, '示例联系人更新', 'infra:demo01-contact:update', 3, 3, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2482, '示例联系人删除', 'infra:demo01-contact:delete', 3, 4, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2483, '示例联系人导出', 'infra:demo01-contact:export', 3, 5, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2484, '树表(增删改查)', '', 2, 2, 1070, 'demo02-category', 'fa:tree', 'infra/demo/demo02/index', 'Demo02Category', 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '1', '2023-11-16 20:35:01', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2485, '示例分类查询', 'infra:demo02-category:query', 3, 1, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2486, '示例分类创建', 'infra:demo02-category:create', 3, 2, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2487, '示例分类更新', 'infra:demo02-category:update', 3, 3, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2488, '示例分类删除', 'infra:demo02-category:delete', 3, 4, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2489, '示例分类导出', 'infra:demo02-category:export', 3, 5, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2490, '主子表(标准)', '', 2, 10, 1070, 'demo03-normal', 'fa:battery-3', 'infra/demo/demo03/normal/index', 'Demo03StudentNormal', 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '1', '2023-11-16 23:10:03', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2491, '学生查询', 'infra:demo03-student:query', 3, 1, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2492, '学生创建', 'infra:demo03-student:create', 3, 2, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2493, '学生更新', 'infra:demo03-student:update', 3, 3, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2494, '学生删除', 'infra:demo03-student:delete', 3, 4, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2495, '学生导出', 'infra:demo03-student:export', 3, 5, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2497, '主子表(ERP)', '', 2, 11, 1070, 'demo03-erp', 'ep:calendar', 'infra/demo/demo03/erp/index', 'Demo03StudentERP', 0, b'1', b'1', b'1', '', '2023-11-16 15:50:59', '1', '2023-11-17 13:19:56', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -2309,7 +2483,7 @@ CREATE TABLE `system_notice`  (
 -- ----------------------------
 BEGIN;
 INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '芋道的公众', '<p>新版本内容133</p>', 1, 0, 'admin', '2021-01-05 17:03:48', '1', '2022-05-04 21:00:20', b'0', 1);
-INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '维护通知:2018-07-01 若依系统凌晨维护', '<p><img src=\"http://test.yudao.iocoder.cn/b7cb3cf49b4b3258bf7309a09dd2f4e5.jpg\">维护内容</p>', 2, 1, 'admin', '2021-01-05 17:03:48', '1', '2022-05-11 12:34:24', b'0', 1);
+INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '维护通知:2018-07-01 若依系统凌晨维护', '<p><img src=\"http://test.yudao.iocoder.cn/b7cb3cf49b4b3258bf7309a09dd2f4e5.jpg\" alt=\"\" data-href=\"\" style=\"\"/>1111</p>', 2, 1, 'admin', '2021-01-05 17:03:48', '1', '2023-11-11 12:51:11', b'0', 1);
 INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, '我是测试标题', '<p>哈哈哈哈123</p>', 1, 0, '110', '2022-02-22 01:01:25', '110', '2022-02-22 01:01:46', b'0', 121);
 COMMIT;
 
@@ -2373,7 +2547,7 @@ CREATE TABLE `system_notify_template`  (
   `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 AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表';
+) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表';
 
 -- ----------------------------
 -- Records of system_notify_template
@@ -2403,7 +2577,7 @@ CREATE TABLE `system_oauth2_access_token`  (
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
   INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3137 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 3467 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_access_token
@@ -2525,7 +2699,7 @@ CREATE TABLE `system_oauth2_refresh_token`  (
   `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 = 1094 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 1115 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_refresh_token
@@ -2565,7 +2739,7 @@ CREATE TABLE `system_operate_log`  (
   `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 = 8766 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 9090 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
 
 -- ----------------------------
 -- Records of system_operate_log
@@ -2591,15 +2765,15 @@ CREATE TABLE `system_post`  (
   `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 = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表';
+) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表';
 
 -- ----------------------------
 -- Records of system_post
 -- ----------------------------
 BEGIN;
 INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'ceo', '董事长', 1, 0, '', 'admin', '2021-01-06 17:03:48', '1', '2023-02-11 15:19:04', b'0', 1);
-INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 'se', '项目经理', 2, 0, '', 'admin', '2021-01-05 17:03:48', '1', '2021-12-12 10:47:47', b'0', 1);
-INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 'user', '普通员工', 4, 0, '111', 'admin', '2021-01-05 17:03:48', '1', '2023-02-11 15:19:00', b'0', 1);
+INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 'se', '项目经理', 2, 0, '', 'admin', '2021-01-05 17:03:48', '1', '2023-11-15 09:18:20', b'0', 1);
+INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 'user', '普通员工', 4, 0, '111', 'admin', '2021-01-05 17:03:48', '1', '2023-11-15 09:18:18', b'0', 1);
 COMMIT;
 
 -- ----------------------------
@@ -2792,10 +2966,6 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1657, 101, 1066, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1658, 101, 1067, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1659, 101, 1070, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1660, 101, 1071, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1661, 101, 1072, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1662, 101, 1073, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1663, 101, 1074, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1664, 101, 1075, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1665, 101, 1076, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1666, 101, 1077, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1);
@@ -3113,10 +3283,6 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2027, 2, 1066, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2028, 2, 1067, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2029, 2, 1070, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2030, 2, 1071, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2031, 2, 1072, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2032, 2, 1073, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2033, 2, 1074, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2034, 2, 1075, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2035, 2, 1076, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2036, 2, 1082, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
@@ -3616,7 +3782,7 @@ CREATE TABLE `system_sms_log`  (
   `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 AUTO_INCREMENT = 449 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 502 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
 
 -- ----------------------------
 -- Records of system_sms_log
@@ -3646,7 +3812,7 @@ CREATE TABLE `system_sms_template`  (
   `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 AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板';
+) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板';
 
 -- ----------------------------
 -- Records of system_sms_template
@@ -3677,6 +3843,7 @@ CREATE TABLE `system_social_client`  (
   `user_type` tinyint NOT NULL COMMENT '用户类型',
   `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号',
   `client_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端密钥',
+  `agent_id` varchar(255) 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 '创建时间',
@@ -3715,7 +3882,7 @@ CREATE TABLE `system_social_user`  (
   `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 = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
+) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
 
 -- ----------------------------
 -- Records of system_social_user
@@ -3740,7 +3907,7 @@ CREATE TABLE `system_social_user_bind`  (
   `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 = 81 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
+) ENGINE = InnoDB AUTO_INCREMENT = 80 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
 
 -- ----------------------------
 -- Records of system_social_user_bind
@@ -3759,7 +3926,7 @@ CREATE TABLE `system_tenant`  (
   `contact_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '联系人',
   `contact_mobile` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '联系手机',
   `status` tinyint NOT NULL DEFAULT 0 COMMENT '租户状态(0正常 1停用)',
-  `domain` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '绑定域名',
+  `website` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '绑定域名',
   `package_id` bigint NOT NULL COMMENT '租户套餐编号',
   `expire_time` datetime NOT NULL COMMENT '过期时间',
   `account_count` int NOT NULL COMMENT '账号数量',
@@ -3775,9 +3942,9 @@ CREATE TABLE `system_tenant`  (
 -- Records of system_tenant
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'https://www.iocoder.cn', 0, '2099-02-19 17:14:16', 9999, '1', '2021-01-05 17:03:47', '1', '2022-02-23 12:15:11', b'0');
-INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'http://www.iocoder.cn', 111, '2024-03-11 00:00:00', 20, '1', '2022-02-22 00:56:14', '1', '2023-09-16 16:59:42', b'0');
-INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'https://www.iocoder.cn', 111, '2022-04-30 00:00:00', 50, '1', '2022-03-07 21:37:58', '1', '2023-09-16 16:59:27', b'0');
+INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'www.iocoder.cn', 0, '2099-02-19 17:14:16', 9999, '1', '2021-01-05 17:03:47', '1', '2023-11-06 11:41:41', b'0');
+INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'zsxq.iocoder.cn', 111, '2024-03-11 00:00:00', 20, '1', '2022-02-22 00:56:14', '1', '2023-11-06 11:41:47', b'0');
+INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'test.iocoder.cn', 111, '2022-04-30 00:00:00', 50, '1', '2022-03-07 21:37:58', '1', '2023-11-06 11:41:53', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -3916,7 +4083,7 @@ CREATE TABLE `system_users`  (
 -- Records of system_users
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://127.0.0.1:48080/admin-api/infra/file/4/get/37e56010ecbee472cdd821ac4b608e151e62a74d9633f15d085aee026eedeb60.png', 0, '0:0:0:0:0:0:0:1', '2023-10-29 17:06:12', 'admin', '2021-01-05 17:03:47', NULL, '2023-10-29 17:06:12', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://127.0.0.1:48080/admin-api/infra/file/4/get/37e56010ecbee472cdd821ac4b608e151e62a74d9633f15d085aee026eedeb60.png', 0, '0:0:0:0:0:0:0:1', '2023-11-18 17:19:30', 'admin', '2021-01-05 17:03:47', NULL, '2023-11-18 17:19:30', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '127.0.0.1', '2022-07-08 01:26:27', '', '2021-01-13 23:50:35', NULL, '2022-07-08 01:26:27', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$10$GP8zvqHB//TekuzYZSBYAuBQJiNq1.fxQVDYJ.uBCOnWCtDVKE4H6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2023-09-24 18:21:19', '', '2021-01-21 02:13:53', NULL, '2023-09-24 18:21:19', b'0', 1);

+ 5 - 5
sql/oracle/ruoyi-vue-pro.sql

@@ -4178,7 +4178,7 @@ CREATE TABLE "SYSTEM_TENANT" (
   "CONTACT_NAME" NVARCHAR2(30),
   "CONTACT_MOBILE" NVARCHAR2(500),
   "STATUS" NUMBER(4,0) NOT NULL,
-  "DOMAIN" NVARCHAR2(256),
+  "WEBSITE" NVARCHAR2(256),
   "PACKAGE_ID" NUMBER(20,0) NOT NULL,
   "EXPIRE_TIME" DATE NOT NULL,
   "ACCOUNT_COUNT" NUMBER(11,0) NOT NULL,
@@ -4211,7 +4211,7 @@ COMMENT ON COLUMN "SYSTEM_TENANT"."CONTACT_USER_ID" IS '联系人的用户编号
 COMMENT ON COLUMN "SYSTEM_TENANT"."CONTACT_NAME" IS '联系人';
 COMMENT ON COLUMN "SYSTEM_TENANT"."CONTACT_MOBILE" IS '联系手机';
 COMMENT ON COLUMN "SYSTEM_TENANT"."STATUS" IS '租户状态(0正常 1停用)';
-COMMENT ON COLUMN "SYSTEM_TENANT"."DOMAIN" IS '绑定域名';
+COMMENT ON COLUMN "SYSTEM_TENANT"."WEBSITE" IS '绑定域名';
 COMMENT ON COLUMN "SYSTEM_TENANT"."PACKAGE_ID" IS '租户套餐编号';
 COMMENT ON COLUMN "SYSTEM_TENANT"."EXPIRE_TIME" IS '过期时间';
 COMMENT ON COLUMN "SYSTEM_TENANT"."ACCOUNT_COUNT" IS '账号数量';
@@ -4225,9 +4225,9 @@ COMMENT ON TABLE "SYSTEM_TENANT" IS '租户表';
 -- ----------------------------
 -- Records of SYSTEM_TENANT
 -- ----------------------------
-INSERT INTO "SYSTEM_TENANT" ("ID", "NAME", "CONTACT_USER_ID", "CONTACT_NAME", "CONTACT_MOBILE", "STATUS", "DOMAIN", "PACKAGE_ID", "EXPIRE_TIME", "ACCOUNT_COUNT", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('1', '芋道源码', NULL, '芋艿', '17321315478', '0', 'https://www.iocoder.cn', '0', TO_DATE('2099-02-19 17:14:16', 'SYYYY-MM-DD HH24:MI:SS'), '9999', '1', TO_DATE('2021-01-05 17:03:47', 'SYYYY-MM-DD HH24:MI:SS'), '1', TO_DATE('2022-02-23 12:15:11', 'SYYYY-MM-DD HH24:MI:SS'), '0');
-INSERT INTO "SYSTEM_TENANT" ("ID", "NAME", "CONTACT_USER_ID", "CONTACT_NAME", "CONTACT_MOBILE", "STATUS", "DOMAIN", "PACKAGE_ID", "EXPIRE_TIME", "ACCOUNT_COUNT", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('121', '小租户', '110', '小王2', '15601691300', '0', 'http://www.iocoder.cn', '111', TO_DATE('2024-03-11 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), '20', '1', TO_DATE('2022-02-22 00:56:14', 'SYYYY-MM-DD HH24:MI:SS'), '1', TO_DATE('2022-03-19 18:37:20', 'SYYYY-MM-DD HH24:MI:SS'), '0');
-INSERT INTO "SYSTEM_TENANT" ("ID", "NAME", "CONTACT_USER_ID", "CONTACT_NAME", "CONTACT_MOBILE", "STATUS", "DOMAIN", "PACKAGE_ID", "EXPIRE_TIME", "ACCOUNT_COUNT", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('122', '测试租户', '113', '芋道', '15601691300', '0', 'https://www.iocoder.cn', '111', TO_DATE('2022-04-30 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), '50', '1', TO_DATE('2022-03-07 21:37:58', 'SYYYY-MM-DD HH24:MI:SS'), '1', TO_DATE('2022-03-07 21:37:58', 'SYYYY-MM-DD HH24:MI:SS'), '0');
+INSERT INTO "SYSTEM_TENANT" ("ID", "NAME", "CONTACT_USER_ID", "CONTACT_NAME", "CONTACT_MOBILE", "STATUS", "WEBSITE", "PACKAGE_ID", "EXPIRE_TIME", "ACCOUNT_COUNT", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('1', '芋道源码', NULL, '芋艿', '17321315478', '0', 'https://www.iocoder.cn', '0', TO_DATE('2099-02-19 17:14:16', 'SYYYY-MM-DD HH24:MI:SS'), '9999', '1', TO_DATE('2021-01-05 17:03:47', 'SYYYY-MM-DD HH24:MI:SS'), '1', TO_DATE('2022-02-23 12:15:11', 'SYYYY-MM-DD HH24:MI:SS'), '0');
+INSERT INTO "SYSTEM_TENANT" ("ID", "NAME", "CONTACT_USER_ID", "CONTACT_NAME", "CONTACT_MOBILE", "STATUS", "WEBSITE", "PACKAGE_ID", "EXPIRE_TIME", "ACCOUNT_COUNT", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('121', '小租户', '110', '小王2', '15601691300', '0', 'http://www.iocoder.cn', '111', TO_DATE('2024-03-11 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), '20', '1', TO_DATE('2022-02-22 00:56:14', 'SYYYY-MM-DD HH24:MI:SS'), '1', TO_DATE('2022-03-19 18:37:20', 'SYYYY-MM-DD HH24:MI:SS'), '0');
+INSERT INTO "SYSTEM_TENANT" ("ID", "NAME", "CONTACT_USER_ID", "CONTACT_NAME", "CONTACT_MOBILE", "STATUS", "WEBSITE", "PACKAGE_ID", "EXPIRE_TIME", "ACCOUNT_COUNT", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('122', '测试租户', '113', '芋道', '15601691300', '0', 'https://www.iocoder.cn', '111', TO_DATE('2022-04-30 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), '50', '1', TO_DATE('2022-03-07 21:37:58', 'SYYYY-MM-DD HH24:MI:SS'), '1', TO_DATE('2022-03-07 21:37:58', 'SYYYY-MM-DD HH24:MI:SS'), '0');
 COMMIT;
 COMMIT;
 

+ 5 - 5
sql/postgresql/ruoyi-vue-pro.sql

@@ -6779,7 +6779,7 @@ CREATE TABLE "system_tenant"
     "contact_name"    varchar(30) COLLATE "pg_catalog"."default" NOT NULL,
     "contact_mobile"  varchar(500) COLLATE "pg_catalog"."default",
     "status"          int2                                       NOT NULL,
-    "domain"          varchar(256) COLLATE "pg_catalog"."default",
+    "website"          varchar(256) COLLATE "pg_catalog"."default",
     "package_id"      int8                                       NOT NULL,
     "expire_time"     timestamp(6)                               NOT NULL,
     "account_count"   int4                                       NOT NULL,
@@ -6803,7 +6803,7 @@ ON COLUMN "system_tenant"."contact_mobile" IS '联系手机';
 COMMENT
 ON COLUMN "system_tenant"."status" IS '租户状态(0正常 1停用)';
 COMMENT
-ON COLUMN "system_tenant"."domain" IS '绑定域名';
+ON COLUMN "system_tenant"."website" IS '绑定域名';
 COMMENT
 ON COLUMN "system_tenant"."package_id" IS '租户套餐编号';
 COMMENT
@@ -6827,17 +6827,17 @@ ON TABLE "system_tenant" IS '租户表';
 -- Records of system_tenant
 -- ----------------------------
 BEGIN;
-INSERT INTO "system_tenant" ("id", "name", "contact_user_id", "contact_name", "contact_mobile", "status", "domain",
+INSERT INTO "system_tenant" ("id", "name", "contact_user_id", "contact_name", "contact_mobile", "status", "website",
                              "package_id", "expire_time", "account_count", "creator", "create_time", "updater",
                              "update_time", "deleted")
 VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'https://www.iocoder.cn', 0, '2099-02-19 17:14:16', 9999, '1',
         '2021-01-05 17:03:47', '1', '2022-02-23 12:15:11', 0);
-INSERT INTO "system_tenant" ("id", "name", "contact_user_id", "contact_name", "contact_mobile", "status", "domain",
+INSERT INTO "system_tenant" ("id", "name", "contact_user_id", "contact_name", "contact_mobile", "status", "website",
                              "package_id", "expire_time", "account_count", "creator", "create_time", "updater",
                              "update_time", "deleted")
 VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'http://www.iocoder.cn', 111, '2024-03-11 00:00:00', 20, '1',
         '2022-02-22 00:56:14', '1', '2022-03-19 18:37:20', 0);
-INSERT INTO "system_tenant" ("id", "name", "contact_user_id", "contact_name", "contact_mobile", "status", "domain",
+INSERT INTO "system_tenant" ("id", "name", "contact_user_id", "contact_name", "contact_mobile", "status", "website",
                              "package_id", "expire_time", "account_count", "creator", "create_time", "updater",
                              "update_time", "deleted")
 VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'https://www.iocoder.cn', 111, '2022-04-30 00:00:00', 50, '1',

+ 5 - 5
sql/sqlserver/ruoyi-vue-pro.sql

@@ -10595,7 +10595,7 @@ CREATE TABLE [dbo].[system_tenant] (
   [contact_name] nvarchar(30) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
   [contact_mobile] nvarchar(500) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
   [status] tinyint  NOT NULL,
-  [domain] nvarchar(256) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
+  [website] nvarchar(256) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
   [package_id] bigint  NOT NULL,
   [expire_time] datetime2(7)  NOT NULL,
   [account_count] int  NOT NULL,
@@ -10656,7 +10656,7 @@ EXEC sp_addextendedproperty
 'MS_Description', N'绑定域名',
 'SCHEMA', N'dbo',
 'TABLE', N'system_tenant',
-'COLUMN', N'domain'
+'COLUMN', N'website'
 GO
 
 EXEC sp_addextendedproperty
@@ -10731,13 +10731,13 @@ GO
 SET IDENTITY_INSERT [dbo].[system_tenant] ON
 GO
 
-INSERT INTO [dbo].[system_tenant] ([id], [name], [contact_user_id], [contact_name], [contact_mobile], [status], [domain], [package_id], [expire_time], [account_count], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'1', N'芋道源码', NULL, N'芋艿', N'17321315478', N'0', N'https://www.iocoder.cn', N'0', N'2099-02-19 17:14:16.0000000', N'9999', N'1', N'2021-01-05 17:03:47.0000000', N'1', N'2022-02-23 12:15:11.0000000', N'0')
+INSERT INTO [dbo].[system_tenant] ([id], [name], [contact_user_id], [contact_name], [contact_mobile], [status], [website], [package_id], [expire_time], [account_count], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'1', N'芋道源码', NULL, N'芋艿', N'17321315478', N'0', N'https://www.iocoder.cn', N'0', N'2099-02-19 17:14:16.0000000', N'9999', N'1', N'2021-01-05 17:03:47.0000000', N'1', N'2022-02-23 12:15:11.0000000', N'0')
 GO
 
-INSERT INTO [dbo].[system_tenant] ([id], [name], [contact_user_id], [contact_name], [contact_mobile], [status], [domain], [package_id], [expire_time], [account_count], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'121', N'小租户', N'110', N'小王2', N'15601691300', N'0', N'http://www.iocoder.cn', N'111', N'2024-03-11 00:00:00.0000000', N'20', N'1', N'2022-02-22 00:56:14.0000000', N'1', N'2022-03-19 18:37:20.0000000', N'0')
+INSERT INTO [dbo].[system_tenant] ([id], [name], [contact_user_id], [contact_name], [contact_mobile], [status], [website], [package_id], [expire_time], [account_count], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'121', N'小租户', N'110', N'小王2', N'15601691300', N'0', N'http://www.iocoder.cn', N'111', N'2024-03-11 00:00:00.0000000', N'20', N'1', N'2022-02-22 00:56:14.0000000', N'1', N'2022-03-19 18:37:20.0000000', N'0')
 GO
 
-INSERT INTO [dbo].[system_tenant] ([id], [name], [contact_user_id], [contact_name], [contact_mobile], [status], [domain], [package_id], [expire_time], [account_count], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'122', N'测试租户', N'113', N'芋道', N'15601691300', N'0', N'https://www.iocoder.cn', N'111', N'2022-04-30 00:00:00.0000000', N'50', N'1', N'2022-03-07 21:37:58.0000000', N'1', N'2022-03-07 21:37:58.0000000', N'0')
+INSERT INTO [dbo].[system_tenant] ([id], [name], [contact_user_id], [contact_name], [contact_mobile], [status], [website], [package_id], [expire_time], [account_count], [creator], [create_time], [updater], [update_time], [deleted]) VALUES (N'122', N'测试租户', N'113', N'芋道', N'15601691300', N'0', N'https://www.iocoder.cn', N'111', N'2022-04-30 00:00:00.0000000', N'50', N'1', N'2022-03-07 21:37:58.0000000', N'1', N'2022-03-07 21:37:58.0000000', N'0')
 GO
 
 SET IDENTITY_INSERT [dbo].[system_tenant] OFF

+ 1 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java

@@ -30,6 +30,7 @@ public interface GlobalErrorCodeConstants {
 
     ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
     ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
+    ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项");
 
     // ========== 自定义错误段 ==========
     ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求

+ 7 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageParam.java

@@ -15,6 +15,13 @@ public class PageParam implements Serializable {
     private static final Integer PAGE_NO = 1;
     private static final Integer PAGE_SIZE = 10;
 
+    /**
+     * 每页条数 - 不分页
+     *
+     * 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。
+     */
+    public static final Integer PAGE_SIZE_NONE = -1;
+
     @Schema(description = "页码,从 1 开始", requiredMode = Schema.RequiredMode.REQUIRED,example = "1")
     @NotNull(message = "页码不能为空")
     @Min(value = 1, message = "页码最小值为 1")

+ 6 - 2
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -224,17 +224,21 @@ public class CollectionUtils {
     }
 
     public static <T> T findFirst(List<T> from, Predicate<T> predicate) {
+        return findFirst(from, predicate, Function.identity());
+    }
+
+    public static <T, U> U findFirst(List<T> from, Predicate<T> predicate, Function<T, U> func) {
         if (CollUtil.isEmpty(from)) {
             return null;
         }
-        return from.stream().filter(predicate).findFirst().orElse(null);
+        return from.stream().filter(predicate).findFirst().map(func).orElse(null);
     }
 
     public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
         if (CollUtil.isEmpty(from)) {
             return null;
         }
-        assert from.size() > 0; // 断言,避免告警
+        assert !from.isEmpty(); // 断言,避免告警
         T t = from.stream().max(Comparator.comparing(valueFunc)).get();
         return valueFunc.apply(t);
     }

+ 30 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.json;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -30,6 +31,7 @@ public class JsonUtils {
     static {
         objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
         objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
         objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
     }
 
@@ -71,6 +73,20 @@ public class JsonUtils {
         }
     }
 
+    public static <T> T parseObject(String text, String path, Class<T> clazz) {
+        if (StrUtil.isEmpty(text)) {
+            return null;
+        }
+        try {
+            JsonNode treeNode = objectMapper.readTree(text);
+            JsonNode pathNode = treeNode.path(path);
+            return objectMapper.readValue(pathNode.toString(), clazz);
+        } catch (IOException e) {
+            log.error("json parse err,json:{}", text, e);
+            throw new RuntimeException(e);
+        }
+    }
+
     public static <T> T parseObject(String text, Type type) {
         if (StrUtil.isEmpty(text)) {
             return null;
@@ -132,6 +148,20 @@ public class JsonUtils {
         }
     }
 
+    public static <T> List<T> parseArray(String text, String path, Class<T> clazz) {
+        if (StrUtil.isEmpty(text)) {
+            return null;
+        }
+        try {
+            JsonNode treeNode = objectMapper.readTree(text);
+            JsonNode pathNode = treeNode.path(path);
+            return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
+        } catch (IOException e) {
+            log.error("json parse err,json:{}", text, e);
+            throw new RuntimeException(e);
+        }
+    }
+
     public static JsonNode parseTree(String text) {
         try {
             return objectMapper.readTree(text);

+ 37 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.framework.common.util.object;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+
+import java.util.List;
+
+/**
+ * Bean 工具类
+ *
+ * 1. 默认使用 {@link cn.hutool.core.bean.BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能
+ * 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现
+ *
+ * @author 芋道源码
+ */
+public class BeanUtils {
+
+    public static <T> T toBean(Object source, Class<T> targetClass) {
+        return BeanUtil.toBean(source, targetClass);
+    }
+
+    public static <S, T> List<T> toBean(List<S> source, Class<T> targetType) {
+        if (source == null) {
+            return null;
+        }
+        return CollectionUtils.convertList(source, s -> toBean(s, targetType));
+    }
+
+    public static  <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {
+        if (source == null) {
+            return null;
+        }
+        return new PageResult<>(toBean(source.getList(), targetType), source.getTotal());
+    }
+
+}

+ 16 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java

@@ -50,4 +50,20 @@ public class StrUtils {
         return Arrays.stream(integers).boxed().collect(Collectors.toList());
     }
 
+    /**
+     * 移除字符串中,包含指定字符串的行
+     *
+     * @param content 字符串
+     * @param sequence 包含的字符串
+     * @return 移除后的字符串
+     */
+    public static String removeLineContains(String content, String sequence) {
+        if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) {
+            return content;
+        }
+        return Arrays.stream(content.split("\n"))
+                .filter(line -> !line.contains(sequence))
+                .collect(Collectors.joining("\n"));
+    }
+
 }

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java

@@ -29,7 +29,7 @@ public class PayTransferRespDTO {
     /**
      * 支付渠道编号
      */
-    private String channelOrderNo;
+    private String channelTransferNo;
 
     /**
      * 支付成功时间
@@ -57,7 +57,7 @@ public class PayTransferRespDTO {
                                              String outTransferNo, Object rawData) {
         PayTransferRespDTO respDTO = new PayTransferRespDTO();
         respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus();
-        respDTO.channelOrderNo = channelOrderNo;
+        respDTO.channelTransferNo = channelOrderNo;
         respDTO.outTransferNo = outTransferNo;
         respDTO.rawData = rawData;
         return respDTO;
@@ -85,7 +85,7 @@ public class PayTransferRespDTO {
                                              String outTransferNo, Object rawData) {
         PayTransferRespDTO respDTO = new PayTransferRespDTO();
         respDTO.status = PayTransferStatusRespEnum.SUCCESS.getStatus();
-        respDTO.channelOrderNo = channelTransferNo;
+        respDTO.channelTransferNo = channelTransferNo;
         respDTO.successTime = successTime;
         // 相对通用的字段
         respDTO.outTransferNo = outTransferNo;

+ 19 - 7
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java

@@ -6,10 +6,13 @@ import lombok.Data;
 import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.util.Map;
 
+import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.*;
+
 /**
  * 统一转账 Request DTO
  *
@@ -48,19 +51,28 @@ public class PayTransferUnifiedReqDTO {
      */
     @NotEmpty(message = "转账标题不能为空")
     @Length(max = 128, message = "转账标题不能超过 128")
-    private String title;
+    private String subject;
 
     /**
-     * 收款方信息。
-     *
-     * 转账类型 {@link #type} 不同,收款方信息不同
+     * 收款人姓名
+     */
+    @NotBlank(message = "收款人姓名不能为空", groups = {Alipay.class})
+    private String userName;
+
+    /**
+     * 支付宝登录号
      */
-    @NotEmpty(message = "收款方信息 不能为空")
-    private Map<String, String> payeeInfo;
+    @NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
+    private String alipayLogonId;
+
+    /**
+     * 微信 openId
+     */
+    @NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
+    private String openid;
 
     /**
      * 支付渠道的额外参数
      */
     private Map<String, String> channelExtras;
-
 }

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

@@ -11,10 +11,13 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReq
 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
+import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 
 import java.util.Map;
 
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
@@ -185,9 +188,9 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
 
     @Override
     public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
-        ValidationUtils.validate(reqDTO);
         PayTransferRespDTO resp;
         try{
+            validatePayTransferReqDTO(reqDTO);
             resp = doUnifiedTransfer(reqDTO);
         }catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
             throw ex;
@@ -199,6 +202,22 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
         }
         return resp;
     }
+    private void validatePayTransferReqDTO(PayTransferUnifiedReqDTO reqDTO) {
+        PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType());
+        switch (transferType) {
+            case ALIPAY_BALANCE: {
+                ValidationUtils.validate(reqDTO,  PayTransferTypeEnum.Alipay.class);
+                break;
+            }
+            case WX_BALANCE: {
+                ValidationUtils.validate(reqDTO, PayTransferTypeEnum.WxPay.class);
+                break;
+            }
+            default: {
+                throw exception(NOT_IMPLEMENTED);
+            }
+        }
+    }
 
     protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO)
             throws Throwable;

+ 8 - 16
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java

@@ -43,7 +43,8 @@ import java.util.Objects;
 import java.util.function.Supplier;
 
 import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
-import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
 import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE;
 
@@ -227,14 +228,13 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException {
         // 1.1 校验公钥类型 必须使用公钥证书模式
         if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
-            throw new IllegalStateException("支付宝单笔转账必须使用公钥证书模式");
+            throw exception0(ERROR_CONFIGURATION.getCode(),"支付宝单笔转账必须使用公钥证书模式");
         }
-
         // 1.2 构建 AlipayFundTransUniTransferModel
         AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
         // ① 通用的参数
         model.setTransAmount(formatAmount(reqDTO.getPrice())); // 转账金额
-        model.setOrderTitle(reqDTO.getTitle());               // 转账业务的标题,用于在支付宝用户的账单里显示。
+        model.setOrderTitle(reqDTO.getSubject());               // 转账业务的标题,用于在支付宝用户的账单里显示。
         model.setOutBizNo(reqDTO.getOutTransferNo());
         model.setProductCode("TRANS_ACCOUNT_NO_PWD");    // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD
         model.setBizScene("DIRECT_TRANSFER");           // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER
@@ -247,16 +247,8 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
                 // ② 个性化的参数
                 Participant payeeInfo = new Participant();
                 payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
-                String logonId = MapUtil.getStr(reqDTO.getPayeeInfo(), "ALIPAY_LOGON_ID");
-                if (StrUtil.isEmpty(logonId)) {
-                    throw exception0(BAD_REQUEST.getCode(), "支付包登录 ID 不能为空");
-                }
-                String accountName = MapUtil.getStr(reqDTO.getPayeeInfo(), "ALIPAY_ACCOUNT_NAME");
-                if (StrUtil.isEmpty(accountName)) {
-                    throw exception0(BAD_REQUEST.getCode(), "支付包账户名称不能为空");
-                }
-                payeeInfo.setIdentity(logonId); // 支付宝登录号
-                payeeInfo.setName(accountName); // 支付宝账号姓名
+                payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号
+                payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名
                 model.setPayeeInfo(payeeInfo);
                 // 1.3 构建 AlipayFundTransUniTransferRequest
                 AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
@@ -279,10 +271,10 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
                 Participant payeeInfo = new Participant();
                 payeeInfo.setIdentityType("BANKCARD_ACCOUNT");
                 // TODO 待实现
-                throw new UnsupportedOperationException("待实现");
+                throw exception(NOT_IMPLEMENTED);
             }
             default: {
-                throw new IllegalStateException("不正确的转账类型: " + transferType);
+                throw exception0(BAD_REQUEST.getCode(),"不正确的转账类型: {}",transferType);
             }
         }
     }

+ 3 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java

@@ -62,4 +62,7 @@ public enum PayChannelEnum {
         return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());
     }
 
+    public static boolean isAlipay(String channelCode) {
+        return channelCode != null && channelCode.startsWith("alipay");
+    }
 }

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java

@@ -14,7 +14,7 @@ import java.util.Objects;
 @AllArgsConstructor
 public enum PayTransferStatusRespEnum {
 
-    WAITING(0, "转账"),
+    WAITING(0, "等待转账"),
 
     /**
      * TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现

+ 5 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferTypeEnum.java

@@ -21,8 +21,11 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
     BANK_CARD(3, "银行卡"),
     WALLET_BALANCE(4, "钱包余额");
 
-    public static final String ALIPAY_LOGON_ID = "ALIPAY_LOGON_ID";
-    public static final String ALIPAY_ACCOUNT_NAME = "ALIPAY_ACCOUNT_NAME";
+    public interface WxPay {
+    }
+
+    public interface Alipay {
+    }
 
     private final Integer type;
     private final String name;

+ 5 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml

@@ -36,19 +36,24 @@
         <dependency>
             <groupId>com.oracle.database.jdbc</groupId>
             <artifactId>ojdbc8</artifactId>
+            <optional>true</optional>
         </dependency>
         <dependency>
             <groupId>org.postgresql</groupId>
             <artifactId>postgresql</artifactId>
+            <optional>true</optional>
         </dependency>
         <dependency>
             <groupId>com.microsoft.sqlserver</groupId>
             <artifactId>mssql-jdbc</artifactId>
+            <optional>true</optional>
         </dependency>
         <dependency>
             <groupId>com.dameng</groupId>
             <artifactId>DmJdbcDriver18</artifactId>
+            <optional>true</optional>
         </dependency>
+
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>druid-spring-boot-starter</artifactId>

+ 24 - 1
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -26,6 +26,12 @@ import java.util.List;
 public interface BaseMapperX<T> extends MPJBaseMapper<T> {
 
     default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
+        // 特殊:不分页,直接查询全部
+        if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageNo())) {
+            List<T> list = selectList(queryWrapper);
+            return new PageResult<>(list, (long) list.size());
+        }
+
         // MyBatis Plus 查询
         IPage<T> mpPage = MyBatisUtils.buildPage(pageParam);
         selectPage(mpPage, queryWrapper);
@@ -93,10 +99,15 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
         return selectList(new LambdaQueryWrapper<T>().in(field, values));
     }
 
+    @Deprecated
     default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {
         return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));
     }
 
+    default List<T> selectList(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
+        return selectList(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
+    }
+
     /**
      * 批量插入,适合大量数据插入
      *
@@ -128,8 +139,20 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
         Db.updateBatchById(entities, size);
     }
 
-    default void saveOrUpdateBatch(Collection<T> collection) {
+    default void insertOrUpdate(T entity) {
+        Db.saveOrUpdate(entity);
+    }
+
+    default void insertOrUpdateBatch(Collection<T> collection) {
         Db.saveOrUpdateBatch(collection);
     }
 
+    default int delete(String field, String value) {
+        return delete(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default int delete(SFunction<T, ?> field, Object value) {
+        return delete(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
 }

+ 19 - 3
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java

@@ -42,9 +42,10 @@ public interface ErrorCodeConstants {
     ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1_003_001_007, "同步失败,不存在改变");
     ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1_003_001_008, "数据库的表注释未填写");
     ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1_003_001_009, "数据库的表字段({})注释未填写");
-
-    // ========== 字典类型(测试)1-001-005-000 ==========
-    ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1_001_005_000, "测试示例不存在");
+    ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_003_001_010, "主表(id={})定义不存在,请检查");
+    ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_003_001_011, "子表的字段(id={})不存在,请检查");
+    ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_003_001_012, "主表生成代码失败,原因:它没有子表");
+    ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_COLUMN = new ErrorCode(1_003_001_013, "主表生成代码失败,原因:它的子表({})没有字段");
 
     // ========== 文件配置 1-001-006-000 ==========
     ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在");
@@ -54,4 +55,19 @@ public interface ErrorCodeConstants {
     ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_007_000, "数据源配置不存在");
     ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1_001_007_001, "数据源配置不正确,无法进行连接");
 
+    // ========== 数据源配置 1-001-107-000 ==========
+    ErrorCode DEMO_STUDENT_NOT_EXISTS = new ErrorCode(1_001_107_000, "学生不存在");
+
+    // ========== 学生 1-001-201-000 ==========
+    ErrorCode DEMO01_CONTACT_NOT_EXISTS = new ErrorCode(1_001_201_000, "示例联系人不存在");
+    ErrorCode DEMO02_CATEGORY_NOT_EXISTS = new ErrorCode(1_001_201_001, "示例分类不存在");
+    ErrorCode DEMO02_CATEGORY_EXITS_CHILDREN = new ErrorCode(1_001_201_002, "存在存在子示例分类,无法删除");
+    ErrorCode DEMO02_CATEGORY_PARENT_NOT_EXITS = new ErrorCode(1_001_201_003,"父级示例分类不存在");
+    ErrorCode DEMO02_CATEGORY_PARENT_ERROR = new ErrorCode(1_001_201_004, "不能设置自己为父示例分类");
+    ErrorCode DEMO02_CATEGORY_NAME_DUPLICATE = new ErrorCode(1_001_201_005, "已经存在该名字的示例分类");
+    ErrorCode DEMO02_CATEGORY_PARENT_IS_CHILD = new ErrorCode(1_001_201_006, "不能设置自己的子示例分类为父示例分类");
+    ErrorCode DEMO03_STUDENT_NOT_EXISTS = new ErrorCode(1_001_201_007, "学生不存在");
+    ErrorCode DEMO03_GRADE_NOT_EXISTS = new ErrorCode(1_001_201_008, "学生班级不存在");
+    ErrorCode DEMO03_GRADE_EXISTS = new ErrorCode(1_001_201_009, "学生班级已存在");
+
 }

+ 10 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/CodegenController.java

@@ -60,10 +60,19 @@ public class CodegenController {
         return success(codegenService.getDatabaseTableList(dataSourceConfigId, name, comment));
     }
 
+    @GetMapping("/table/list")
+    @Operation(summary = "获得表定义列表")
+    @Parameter(name = "dataSourceConfigId", description = "数据源配置的编号", required = true, example = "1")
+    @PreAuthorize("@ss.hasPermission('infra:codegen:query')")
+    public CommonResult<List<CodegenTableRespVO>> getCodegenTableList(@RequestParam(value = "dataSourceConfigId") Long dataSourceConfigId) {
+        List<CodegenTableDO> list = codegenService.getCodegenTableList(dataSourceConfigId);
+        return success(CodegenConvert.INSTANCE.convertList05(list));
+    }
+
     @GetMapping("/table/page")
     @Operation(summary = "获得表定义分页")
     @PreAuthorize("@ss.hasPermission('infra:codegen:query')")
-    public CommonResult<PageResult<CodegenTableRespVO>> getCodeGenTablePage(@Valid CodegenTablePageReqVO pageReqVO) {
+    public CommonResult<PageResult<CodegenTableRespVO>> getCodegenTablePage(@Valid CodegenTablePageReqVO pageReqVO) {
         PageResult<CodegenTableDO> pageResult = codegenService.getCodegenTablePage(pageReqVO);
         return success(CodegenConvert.INSTANCE.convertPage(pageResult));
     }

+ 17 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java

@@ -4,6 +4,8 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnBaseVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableBaseVO;
 import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -37,12 +39,27 @@ public class CodegenUpdateReqVO {
         private Long id;
 
         @AssertTrue(message = "上级菜单不能为空,请前往 [修改生成配置 -> 生成信息] 界面,设置“上级菜单”字段")
+        @JsonIgnore
         public boolean isParentMenuIdValid() {
             // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的
             return ObjectUtil.notEqual(getScene(), CodegenSceneEnum.ADMIN.getScene())
                     || getParentMenuId() != null;
         }
 
+        @AssertTrue(message = "关联的父表信息不全")
+        @JsonIgnore
+        public boolean isSubValid() {
+            return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.SUB)
+                    || (ObjectUtil.isAllNotEmpty(getMasterTableId(), getSubJoinColumnId(), getSubJoinMany()));
+        }
+
+        @AssertTrue(message = "关联的树表信息不全")
+        @JsonIgnore
+        public boolean isTreeValid() {
+            return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.TREE)
+                    || (ObjectUtil.isAllNotEmpty(getTreeParentColumnId(), getTreeNameColumnId()));
+        }
+
     }
 
     @Schema(description = "更新表定义")

+ 12 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableBaseVO.java

@@ -58,4 +58,16 @@ public class CodegenTableBaseVO {
     @Schema(description = "父菜单编号", example = "1024")
     private Long parentMenuId;
 
+    @Schema(description = "主表的编号", example = "2048")
+    private Long masterTableId;
+    @Schema(description = "子表关联主表的字段编号", example = "4096")
+    private Long subJoinColumnId;
+    @Schema(description = "主表与子表是否一对多", example = "4096")
+    private Boolean subJoinMany;
+
+    @Schema(description = "树表的父字段编号", example = "8192")
+    private Long treeParentColumnId;
+    @Schema(description = "树表的名字字段编号", example = "16384")
+    private Long treeNameColumnId;
+
 }

+ 93 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/Demo01ContactController.java

@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo01;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo.Demo01ContactRespVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo.Demo01ContactSaveReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;
+import cn.iocoder.yudao.module.infra.service.demo.demo01.Demo01ContactService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+@Tag(name = "管理后台 - 示例联系人")
+@RestController
+@RequestMapping("/infra/demo01-contact")
+@Validated
+public class Demo01ContactController {
+
+    @Resource
+    private Demo01ContactService demo01ContactService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建示例联系人")
+    @PreAuthorize("@ss.hasPermission('infra:demo01-contact:create')")
+    public CommonResult<Long> createDemo01Contact(@Valid @RequestBody Demo01ContactSaveReqVO createReqVO) {
+        return success(demo01ContactService.createDemo01Contact(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新示例联系人")
+    @PreAuthorize("@ss.hasPermission('infra:demo01-contact:update')")
+    public CommonResult<Boolean> updateDemo01Contact(@Valid @RequestBody Demo01ContactSaveReqVO updateReqVO) {
+        demo01ContactService.updateDemo01Contact(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除示例联系人")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('infra:demo01-contact:delete')")
+    public CommonResult<Boolean> deleteDemo01Contact(@RequestParam("id") Long id) {
+        demo01ContactService.deleteDemo01Contact(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得示例联系人")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('infra:demo01-contact:query')")
+    public CommonResult<Demo01ContactRespVO> getDemo01Contact(@RequestParam("id") Long id) {
+        Demo01ContactDO demo01Contact = demo01ContactService.getDemo01Contact(id);
+        return success(BeanUtils.toBean(demo01Contact, Demo01ContactRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得示例联系人分页")
+    @PreAuthorize("@ss.hasPermission('infra:demo01-contact:query')")
+    public CommonResult<PageResult<Demo01ContactRespVO>> getDemo01ContactPage(@Valid Demo01ContactPageReqVO pageReqVO) {
+        PageResult<Demo01ContactDO> pageResult = demo01ContactService.getDemo01ContactPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, Demo01ContactRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出示例联系人 Excel")
+    @PreAuthorize("@ss.hasPermission('infra:demo01-contact:export')")
+    @OperateLog(type = EXPORT)
+    public void exportDemo01ContactExcel(@Valid Demo01ContactPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<Demo01ContactDO> list = demo01ContactService.getDemo01ContactPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "示例联系人.xls", "数据", Demo01ContactRespVO.class,
+                        BeanUtils.toBean(list, Demo01ContactRespVO.class));
+    }
+
+}

+ 28 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/vo/Demo01ContactPageReqVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 示例联系人分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class Demo01ContactPageReqVO extends PageParam {
+
+    @Schema(description = "名字", example = "张三")
+    private String name;
+
+    @Schema(description = "性别", example = "1")
+    private Integer sex;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 47 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/vo/Demo01ContactRespVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+@Schema(description = "管理后台 - 示例联系人 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class Demo01ContactRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21555")
+    @ExcelProperty("编号")
+    private Long id;
+
+    @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @ExcelProperty("名字")
+    private String name;
+
+    @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty(value = "性别", converter = DictConvert.class)
+    @DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+    private Integer sex;
+
+    @Schema(description = "出生年", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("出生年")
+    private LocalDateTime birthday;
+
+    @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
+    @ExcelProperty("简介")
+    private String description;
+
+    @Schema(description = "头像")
+    @ExcelProperty("头像")
+    private String avatar;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 36 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/vo/Demo01ContactSaveReqVO.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 示例联系人新增/修改 Request VO")
+@Data
+public class Demo01ContactSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21555")
+    private Long id;
+
+    @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @NotEmpty(message = "名字不能为空")
+    private String name;
+
+    @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "性别不能为空")
+    private Integer sex;
+
+    @Schema(description = "出生年", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "出生年不能为空")
+    private LocalDateTime birthday;
+
+    @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
+    @NotEmpty(message = "简介不能为空")
+    private String description;
+
+    @Schema(description = "头像")
+    private String avatar;
+
+}

+ 90 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/Demo02CategoryController.java

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo02;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryRespVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo.Demo02CategorySaveReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;
+import cn.iocoder.yudao.module.infra.service.demo.demo02.Demo02CategoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+@Tag(name = "管理后台 - 示例分类")
+@RestController
+@RequestMapping("/infra/demo02-category")
+@Validated
+public class Demo02CategoryController {
+
+    @Resource
+    private Demo02CategoryService demo02CategoryService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建示例分类")
+    @PreAuthorize("@ss.hasPermission('infra:demo02-category:create')")
+    public CommonResult<Long> createDemo02Category(@Valid @RequestBody Demo02CategorySaveReqVO createReqVO) {
+        return success(demo02CategoryService.createDemo02Category(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新示例分类")
+    @PreAuthorize("@ss.hasPermission('infra:demo02-category:update')")
+    public CommonResult<Boolean> updateDemo02Category(@Valid @RequestBody Demo02CategorySaveReqVO updateReqVO) {
+        demo02CategoryService.updateDemo02Category(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除示例分类")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('infra:demo02-category:delete')")
+    public CommonResult<Boolean> deleteDemo02Category(@RequestParam("id") Long id) {
+        demo02CategoryService.deleteDemo02Category(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得示例分类")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('infra:demo02-category:query')")
+    public CommonResult<Demo02CategoryRespVO> getDemo02Category(@RequestParam("id") Long id) {
+        Demo02CategoryDO demo02Category = demo02CategoryService.getDemo02Category(id);
+        return success(BeanUtils.toBean(demo02Category, Demo02CategoryRespVO.class));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得示例分类列表")
+    @PreAuthorize("@ss.hasPermission('infra:demo02-category:query')")
+    public CommonResult<List<Demo02CategoryRespVO>> getDemo02CategoryList(@Valid Demo02CategoryListReqVO listReqVO) {
+        List<Demo02CategoryDO> list = demo02CategoryService.getDemo02CategoryList(listReqVO);
+        return success(BeanUtils.toBean(list, Demo02CategoryRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出示例分类 Excel")
+    @PreAuthorize("@ss.hasPermission('infra:demo02-category:export')")
+    @OperateLog(type = EXPORT)
+    public void exportDemo02CategoryExcel(@Valid Demo02CategoryListReqVO listReqVO,
+              HttpServletResponse response) throws IOException {
+        List<Demo02CategoryDO> list = demo02CategoryService.getDemo02CategoryList(listReqVO);
+        // 导出 Excel
+        ExcelUtils.write(response, "示例分类.xls", "数据", Demo02CategoryRespVO.class,
+                        BeanUtils.toBean(list, Demo02CategoryRespVO.class));
+    }
+
+}

+ 25 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/vo/Demo02CategoryListReqVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 示例分类列表 Request VO")
+@Data
+public class Demo02CategoryListReqVO {
+
+    @Schema(description = "名字", example = "芋艿")
+    private String name;
+
+    @Schema(description = "父级编号", example = "6080")
+    private Long parentId;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 31 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/vo/Demo02CategoryRespVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 示例分类 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class Demo02CategoryRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10304")
+    @ExcelProperty("编号")
+    private Long id;
+
+    @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @ExcelProperty("名字")
+    private String name;
+
+    @Schema(description = "父级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6080")
+    @ExcelProperty("父级编号")
+    private Long parentId;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 24 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo02/vo/Demo02CategorySaveReqVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 示例分类新增/修改 Request VO")
+@Data
+public class Demo02CategorySaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10304")
+    private Long id;
+
+    @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @NotEmpty(message = "名字不能为空")
+    private String name;
+
+    @Schema(description = "父级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6080")
+    @NotNull(message = "父级编号不能为空")
+    private Long parentId;
+
+}

+ 197 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/Demo03StudentController.java

@@ -0,0 +1,197 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo.Demo03StudentPageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo.Demo03StudentRespVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo.Demo03StudentSaveReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
+import cn.iocoder.yudao.module.infra.service.demo.demo03.Demo03StudentService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+@Tag(name = "管理后台 - 学生")
+@RestController
+@RequestMapping("/infra/demo03-student")
+@Validated
+public class Demo03StudentController {
+
+    @Resource
+    private Demo03StudentService demo03StudentService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建学生")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:create')")
+    public CommonResult<Long> createDemo03Student(@Valid @RequestBody Demo03StudentSaveReqVO createReqVO) {
+        return success(demo03StudentService.createDemo03Student(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新学生")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:update')")
+    public CommonResult<Boolean> updateDemo03Student(@Valid @RequestBody Demo03StudentSaveReqVO updateReqVO) {
+        demo03StudentService.updateDemo03Student(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除学生")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
+    public CommonResult<Boolean> deleteDemo03Student(@RequestParam("id") Long id) {
+        demo03StudentService.deleteDemo03Student(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得学生")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
+    public CommonResult<Demo03StudentRespVO> getDemo03Student(@RequestParam("id") Long id) {
+        Demo03StudentDO demo03Student = demo03StudentService.getDemo03Student(id);
+        return success(BeanUtils.toBean(demo03Student, Demo03StudentRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得学生分页")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
+    public CommonResult<PageResult<Demo03StudentRespVO>> getDemo03StudentPage(@Valid Demo03StudentPageReqVO pageReqVO) {
+        PageResult<Demo03StudentDO> pageResult = demo03StudentService.getDemo03StudentPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, Demo03StudentRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出学生 Excel")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:export')")
+    @OperateLog(type = EXPORT)
+    public void exportDemo03StudentExcel(@Valid Demo03StudentPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<Demo03StudentDO> list = demo03StudentService.getDemo03StudentPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "学生.xls", "数据", Demo03StudentRespVO.class,
+                        BeanUtils.toBean(list, Demo03StudentRespVO.class));
+    }
+
+    // ==================== 子表(学生课程) ====================
+
+    @GetMapping("/demo03-course/page")
+    @Operation(summary = "获得学生课程分页")
+    @Parameter(name = "studentId", description = "学生编号")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
+    public CommonResult<PageResult<Demo03CourseDO>> getDemo03CoursePage(PageParam pageReqVO,
+                                                                        @RequestParam("studentId") Long studentId) {
+        return success(demo03StudentService.getDemo03CoursePage(pageReqVO, studentId));
+    }
+
+    @PostMapping("/demo03-course/create")
+    @Operation(summary = "创建学生课程")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:create')")
+    public CommonResult<Long> createDemo03Course(@Valid @RequestBody Demo03CourseDO demo03Course) {
+        return success(demo03StudentService.createDemo03Course(demo03Course));
+    }
+
+    @PutMapping("/demo03-course/update")
+    @Operation(summary = "更新学生课程")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:update')")
+    public CommonResult<Boolean> updateDemo03Course(@Valid @RequestBody Demo03CourseDO demo03Course) {
+        demo03StudentService.updateDemo03Course(demo03Course);
+        return success(true);
+    }
+
+    @DeleteMapping("/demo03-course/delete")
+    @Parameter(name = "id", description = "编号", required = true)
+    @Operation(summary = "删除学生课程")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
+    public CommonResult<Boolean> deleteDemo03Course(@RequestParam("id") Long id) {
+        demo03StudentService.deleteDemo03Course(id);
+        return success(true);
+    }
+
+    @GetMapping("/demo03-course/get")
+    @Operation(summary = "获得学生课程")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
+    public CommonResult<Demo03CourseDO> getDemo03Course(@RequestParam("id") Long id) {
+        return success(demo03StudentService.getDemo03Course(id));
+    }
+
+    @GetMapping("/demo03-course/list-by-student-id")
+    @Operation(summary = "获得学生课程列表")
+    @Parameter(name = "studentId", description = "学生编号")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
+    public CommonResult<List<Demo03CourseDO>> getDemo03CourseListByStudentId(@RequestParam("studentId") Long studentId) {
+        return success(demo03StudentService.getDemo03CourseListByStudentId(studentId));
+    }
+
+    // ==================== 子表(学生班级) ====================
+
+    @GetMapping("/demo03-grade/page")
+    @Operation(summary = "获得学生班级分页")
+    @Parameter(name = "studentId", description = "学生编号")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
+    public CommonResult<PageResult<Demo03GradeDO>> getDemo03GradePage(PageParam pageReqVO,
+                                                                      @RequestParam("studentId") Long studentId) {
+        return success(demo03StudentService.getDemo03GradePage(pageReqVO, studentId));
+    }
+
+    @PostMapping("/demo03-grade/create")
+    @Operation(summary = "创建学生班级")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:create')")
+    public CommonResult<Long> createDemo03Grade(@Valid @RequestBody Demo03GradeDO demo03Grade) {
+        return success(demo03StudentService.createDemo03Grade(demo03Grade));
+    }
+
+    @PutMapping("/demo03-grade/update")
+    @Operation(summary = "更新学生班级")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:update')")
+    public CommonResult<Boolean> updateDemo03Grade(@Valid @RequestBody Demo03GradeDO demo03Grade) {
+        demo03StudentService.updateDemo03Grade(demo03Grade);
+        return success(true);
+    }
+
+    @DeleteMapping("/demo03-grade/delete")
+    @Parameter(name = "id", description = "编号", required = true)
+    @Operation(summary = "删除学生班级")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
+    public CommonResult<Boolean> deleteDemo03Grade(@RequestParam("id") Long id) {
+        demo03StudentService.deleteDemo03Grade(id);
+        return success(true);
+    }
+
+    @GetMapping("/demo03-grade/get")
+    @Operation(summary = "获得学生班级")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
+    public CommonResult<Demo03GradeDO> getDemo03Grade(@RequestParam("id") Long id) {
+        return success(demo03StudentService.getDemo03Grade(id));
+    }
+
+    @GetMapping("/demo03-grade/get-by-student-id")
+    @Operation(summary = "获得学生班级")
+    @Parameter(name = "studentId", description = "学生编号")
+    @PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
+    public CommonResult<Demo03GradeDO> getDemo03GradeByStudentId(@RequestParam("studentId") Long studentId) {
+        return success(demo03StudentService.getDemo03GradeByStudentId(studentId));
+    }
+
+}

+ 1 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03;

+ 30 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/vo/Demo03StudentPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 学生分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class Demo03StudentPageReqVO extends PageParam {
+
+    @Schema(description = "名字", example = "芋艿")
+    private String name;
+
+    @Schema(description = "性别")
+    private Integer sex;
+
+    @Schema(description = "简介", example = "随便")
+    private String description;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 41 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/vo/Demo03StudentRespVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+@Schema(description = "管理后台 - 学生 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class Demo03StudentRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8525")
+    @ExcelProperty("编号")
+    private Long id;
+
+    @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @ExcelProperty("名字")
+    private String name;
+
+    @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty(value = "性别", converter = DictConvert.class)
+    @DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+    private Integer sex;
+
+    @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("出生日期")
+    private LocalDateTime birthday;
+
+    @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")
+    @ExcelProperty("简介")
+    private String description;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 39 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/vo/Demo03StudentSaveReqVO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+import java.time.LocalDateTime;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
+
+@Schema(description = "管理后台 - 学生新增/修改 Request VO")
+@Data
+public class Demo03StudentSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8525")
+    private Long id;
+
+    @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @NotEmpty(message = "名字不能为空")
+    private String name;
+
+    @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "性别不能为空")
+    private Integer sex;
+
+    @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "出生日期不能为空")
+    private LocalDateTime birthday;
+
+    @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")
+    @NotEmpty(message = "简介不能为空")
+    private String description;
+
+
+    private List<Demo03CourseDO> demo03Courses;
+
+    private Demo03GradeDO demo03Grade;
+
+}

+ 8 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/package-info.java

@@ -0,0 +1,8 @@
+/**
+ * 代码生成示例
+ *
+ * 1. demo01:单表(增删改查)
+ * 2. demo02:单表(树形结构)
+ * 3. demo03:主子表(标准模式)+ 主子表(ERP 模式)+ 主子表(内嵌模式)
+ */
+package cn.iocoder.yudao.module.infra.controller.admin.demo;

+ 0 - 19
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/TestDemoController.http

@@ -1,19 +0,0 @@
-### 请求 /infra/test-demo/get 接口 => 成功
-GET {{baseUrl}}/infra/test-demo/get?id=106
-Authorization: Bearer {{token}}
-tenant-id: {{adminTenentId}}
-
-### 请求 /infra/test-demo/update 接口 => 成功
-PUT {{baseUrl}}/infra/test-demo/update
-Authorization: Bearer {{token}}
-tenant-id: {{adminTenentId}}
-Content-Type: application/json
-
-
-{
-  "id": 106,
-  "name": "测试",
-  "status": "0",
-  "type": 1,
-  "category": 1
-}

+ 0 - 97
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/TestDemoController.java

@@ -1,97 +0,0 @@
-package cn.iocoder.yudao.module.infra.controller.admin.test;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
-import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.*;
-import cn.iocoder.yudao.module.infra.convert.test.TestDemoConvert;
-import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
-import cn.iocoder.yudao.module.infra.service.test.TestDemoService;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Operation;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletResponse;
-import javax.validation.Valid;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
-
-@Tag(name = "管理后台 - 字典类型")
-@RestController
-@RequestMapping("/infra/test-demo")
-@Validated
-public class TestDemoController {
-
-    @Resource
-    private TestDemoService testDemoService;
-
-    @PostMapping("/create")
-    @Operation(summary = "创建字典类型")
-    @PreAuthorize("@ss.hasPermission('infra:test-demo:create')")
-    public CommonResult<Long> createTestDemo(@Valid @RequestBody TestDemoCreateReqVO createReqVO) {
-        return success(testDemoService.createTestDemo(createReqVO));
-    }
-
-    @PutMapping("/update")
-    @Operation(summary = "更新字典类型")
-    @PreAuthorize("@ss.hasPermission('infra:test-demo:update')")
-    public CommonResult<Boolean> updateTestDemo(@Valid @RequestBody TestDemoUpdateReqVO updateReqVO) {
-        testDemoService.updateTestDemo(updateReqVO);
-        return success(true);
-    }
-
-    @DeleteMapping("/delete")
-    @Operation(summary = "删除字典类型")
-    @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('infra:test-demo:delete')")
-    public CommonResult<Boolean> deleteTestDemo(@RequestParam("id") Long id) {
-        testDemoService.deleteTestDemo(id);
-        return success(true);
-    }
-
-    @GetMapping("/get")
-    @Operation(summary = "获得字典类型")
-    @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('infra:test-demo:query')")
-    public CommonResult<TestDemoRespVO> getTestDemo(@RequestParam("id") Long id) {
-        TestDemoDO testDemo = testDemoService.getTestDemo(id);
-        return success(TestDemoConvert.INSTANCE.convert(testDemo));
-    }
-
-    @GetMapping("/list")
-    @Operation(summary = "获得字典类型列表")
-    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
-    @PreAuthorize("@ss.hasPermission('infra:test-demo:query')")
-    public CommonResult<List<TestDemoRespVO>> getTestDemoList(@RequestParam("ids") Collection<Long> ids) {
-        List<TestDemoDO> list = testDemoService.getTestDemoList(ids);
-        return success(TestDemoConvert.INSTANCE.convertList(list));
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得字典类型分页")
-    @PreAuthorize("@ss.hasPermission('infra:test-demo:query')")    public CommonResult<PageResult<TestDemoRespVO>> getTestDemoPage(@Valid TestDemoPageReqVO pageVO) {
-        PageResult<TestDemoDO> pageResult = testDemoService.getTestDemoPage(pageVO);
-        return success(TestDemoConvert.INSTANCE.convertPage(pageResult));
-    }
-
-    @GetMapping("/export-excel")
-    @Operation(summary = "导出字典类型 Excel")
-    @PreAuthorize("@ss.hasPermission('infra:test-demo:export')")    @OperateLog(type = EXPORT)
-    public void exportTestDemoExcel(@Valid TestDemoExportReqVO exportReqVO,
-              HttpServletResponse response) throws IOException {
-        List<TestDemoDO> list = testDemoService.getTestDemoList(exportReqVO);
-        // 导出 Excel
-        List<TestDemoExcelVO> datas = TestDemoConvert.INSTANCE.convertList02(list);
-        ExcelUtils.write(response, "字典类型.xls", "数据", TestDemoExcelVO.class, datas);
-    }
-
-}

+ 0 - 32
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoBaseVO.java

@@ -1,32 +0,0 @@
-package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import javax.validation.constraints.*;
-
-/**
-* 字典类型 Base VO,提供给添加、修改、详细的子 VO 使用
-* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
-*/
-@Data
-public class TestDemoBaseVO {
-
-    @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "名字不能为空")
-    private String name;
-
-    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "状态不能为空")
-    private Integer status;
-
-    @Schema(description = "类型", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "类型不能为空")
-    private Integer type;
-
-    @Schema(description = "分类", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "分类不能为空")
-    private Integer category;
-
-    @Schema(description = "备注")
-    private String remark;
-
-}

+ 0 - 11
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoCreateReqVO.java

@@ -1,11 +0,0 @@
-package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-
-@Schema(description = "管理后台 - 字典类型创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class TestDemoCreateReqVO extends TestDemoBaseVO {
-
-}

+ 0 - 38
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoExcelVO.java

@@ -1,38 +0,0 @@
-package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
-
-import lombok.*;
-
-import java.time.LocalDateTime;
-
-import com.alibaba.excel.annotation.ExcelProperty;
-
-/**
- * 字典类型 Excel VO
- *
- * @author 芋道源码
- */
-@Data
-public class TestDemoExcelVO {
-
-    @ExcelProperty("编号")
-    private Long id;
-
-    @ExcelProperty("名字")
-    private String name;
-
-    @ExcelProperty("状态")
-    private Integer status;
-
-    @ExcelProperty("类型")
-    private Integer type;
-
-    @ExcelProperty("分类")
-    private Integer category;
-
-    @ExcelProperty("备注")
-    private String remark;
-
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
-
-}

+ 0 - 33
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoExportReqVO.java

@@ -1,33 +0,0 @@
-package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-
-import java.time.LocalDateTime;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-@Schema(description = "管理后台 - 字典类型 Excel 导出 Request VO,参数和 TestDemoPageReqVO 是一致的")
-@Data
-public class TestDemoExportReqVO {
-
-    @Schema(description = "名字")
-    private String name;
-
-    @Schema(description = "状态")
-    private Integer status;
-
-    @Schema(description = "类型")
-    private Integer type;
-
-    @Schema(description = "分类")
-    private Integer category;
-
-    @Schema(description = "备注")
-    private String remark;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "创建时间")
-    private LocalDateTime[] createTime;
-
-}

+ 0 - 36
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoPageReqVO.java

@@ -1,36 +0,0 @@
-package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-
-import java.time.LocalDateTime;
-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;
-
-@Schema(description = "管理后台 - 字典类型分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class TestDemoPageReqVO extends PageParam {
-
-    @Schema(description = "名字")
-    private String name;
-
-    @Schema(description = "状态")
-    private Integer status;
-
-    @Schema(description = "类型")
-    private Integer type;
-
-    @Schema(description = "分类")
-    private Integer category;
-
-    @Schema(description = "备注")
-    private String remark;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "创建时间")
-    private LocalDateTime[] createTime;
-
-}

+ 0 - 19
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoRespVO.java

@@ -1,19 +0,0 @@
-package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-
-import java.time.LocalDateTime;
-
-@Schema(description = "管理后台 - 字典类型 Response VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class TestDemoRespVO extends TestDemoBaseVO {
-
-    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Long id;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
-
-}

+ 0 - 16
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/test/vo/TestDemoUpdateReqVO.java

@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.infra.controller.admin.test.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import javax.validation.constraints.*;
-
-@Schema(description = "管理后台 - 字典类型更新 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class TestDemoUpdateReqVO extends TestDemoBaseVO {
-
-    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "编号不能为空")
-    private Long id;
-
-}

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/codegen/CodegenConvert.java

@@ -56,7 +56,7 @@ public interface CodegenConvert {
 
     // ========== CodegenTableDO 相关 ==========
 
-//    List<CodegenTableRespVO> convertList02(List<CodegenTableDO> list);
+    List<CodegenTableRespVO> convertList05(List<CodegenTableDO> list);
 
     CodegenTableRespVO convert(CodegenTableDO bean);
 

+ 0 - 36
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/test/TestDemoConvert.java

@@ -1,36 +0,0 @@
-package cn.iocoder.yudao.module.infra.convert.test;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoCreateReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExcelVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoRespVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoUpdateReqVO;
-import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-
-/**
- * 字典类型 Convert
- *
- * @author 芋道源码
- */
-@Mapper
-public interface TestDemoConvert {
-
-    TestDemoConvert INSTANCE = Mappers.getMapper(TestDemoConvert.class);
-
-    TestDemoDO convert(TestDemoCreateReqVO bean);
-
-    TestDemoDO convert(TestDemoUpdateReqVO bean);
-
-    TestDemoRespVO convert(TestDemoDO bean);
-
-    List<TestDemoRespVO> convertList(List<TestDemoDO> list);
-
-    PageResult<TestDemoRespVO> convertPage(PageResult<TestDemoDO> page);
-
-    List<TestDemoExcelVO> convertList02(List<TestDemoDO> list);
-
-}

+ 39 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenTableDO.java

@@ -116,4 +116,43 @@ public class CodegenTableDO extends BaseDO {
      */
     private Long parentMenuId;
 
+    // ========== 主子表相关字段 ==========
+
+    /**
+     * 主表的编号
+     *
+     * 关联 {@link CodegenTableDO#getId()}
+     */
+    private Long masterTableId;
+    /**
+     * 【自己】子表关联主表的字段编号
+     *
+     * 关联 {@link CodegenColumnDO#getId()}
+     */
+    private Long subJoinColumnId;
+    /**
+     * 主表与子表是否一对多
+     *
+     * true:一对多
+     * false:一对一
+     */
+    private Boolean subJoinMany;
+
+    // ========== 树表相关字段 ==========
+
+    /**
+     * 树表的父字段编号
+     *
+     * 关联 {@link CodegenColumnDO#getId()}
+     */
+    private Long treeParentColumnId;
+    /**
+     * 树表的名字字段编号
+     *
+     * 名字的用途:新增或修改时,select 框展示的字段
+     *
+     * 关联 {@link CodegenColumnDO#getId()}
+     */
+    private Long treeNameColumnId;
+
 }

+ 54 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo01/Demo01ContactDO.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo01;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 示例联系人 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_demo01_contact")
+@KeySequence("infra_demo01_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Demo01ContactDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 名字
+     */
+    private String name;
+    /**
+     * 性别
+     *
+     * 枚举 {@link TODO system_user_sex 对应的类}
+     */
+    private Integer sex;
+    /**
+     * 出生年
+     */
+    private LocalDateTime birthday;
+    /**
+     * 简介
+     */
+    private String description;
+    /**
+     * 头像
+     */
+    private String avatar;
+
+}

+ 41 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo02/Demo02CategoryDO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo02;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 示例分类 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_demo02_category")
+@KeySequence("infra_demo02_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Demo02CategoryDO extends BaseDO {
+
+    public static final Long PARENT_ID_ROOT = 0L;
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 名字
+     */
+    private String name;
+    /**
+     * 父级编号
+     */
+    private Long parentId;
+
+}

+ 43 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo03/Demo03CourseDO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生课程 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_demo03_course")
+@KeySequence("infra_demo03_course_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Demo03CourseDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 学生编号
+     */
+    private Long studentId;
+    /**
+     * 名字
+     */
+    private String name;
+    /**
+     * 分数
+     */
+    private Integer score;
+
+}

+ 43 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo03/Demo03GradeDO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生班级 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_demo03_grade")
+@KeySequence("infra_demo03_grade_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Demo03GradeDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 学生编号
+     */
+    private Long studentId;
+    /**
+     * 名字
+     */
+    private String name;
+    /**
+     * 班主任
+     */
+    private String teacher;
+
+}

+ 50 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/demo03/Demo03StudentDO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_demo03_student")
+@KeySequence("infra_demo03_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Demo03StudentDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 名字
+     */
+    private String name;
+    /**
+     * 性别
+     *
+     * 枚举 {@link TODO system_user_sex 对应的类}
+     */
+    private Integer sex;
+    /**
+     * 出生日期
+     */
+    private LocalDateTime birthday;
+    /**
+     * 简介
+     */
+    private String description;
+
+}

+ 0 - 50
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/test/TestDemoDO.java

@@ -1,50 +0,0 @@
-package cn.iocoder.yudao.module.infra.dal.dataobject.test;
-
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.*;
-
-/**
- * 字典类型 DO
- *
- * @author 芋道源码
- */
-@TableName("infra_test_demo")
-@KeySequence("infra_test_demo_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class TestDemoDO extends BaseDO {
-
-    /**
-     * 编号
-     */
-    @TableId
-    private Long id;
-    /**
-     * 名字
-     */
-    private String name;
-    /**
-     * 状态
-     */
-    private Integer status;
-    /**
-     * 类型
-     */
-    private Integer type;
-    /**
-     * 分类
-     */
-    private Integer category;
-    /**
-     * 备注
-     */
-    private String remark;
-
-}

+ 5 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/codegen/CodegenTableMapper.java

@@ -29,4 +29,9 @@ public interface CodegenTableMapper extends BaseMapperX<CodegenTableDO> {
         return selectList(CodegenTableDO::getDataSourceConfigId, dataSourceConfigId);
     }
 
+    default List<CodegenTableDO> selectListByTemplateTypeAndMasterTableId(Integer templateType, Long masterTableId) {
+        return selectList(CodegenTableDO::getTemplateType, templateType,
+                CodegenTableDO::getMasterTableId, masterTableId);
+    }
+
 }

+ 26 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo01/Demo01ContactMapper.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo.demo01;
+
+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.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 示例联系人 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface Demo01ContactMapper extends BaseMapperX<Demo01ContactDO> {
+
+    default PageResult<Demo01ContactDO> selectPage(Demo01ContactPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<Demo01ContactDO>()
+                .likeIfPresent(Demo01ContactDO::getName, reqVO.getName())
+                .eqIfPresent(Demo01ContactDO::getSex, reqVO.getSex())
+                .betweenIfPresent(Demo01ContactDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(Demo01ContactDO::getId));
+    }
+
+}

+ 35 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo02/Demo02CategoryMapper.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo.demo02;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 示例分类 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface Demo02CategoryMapper extends BaseMapperX<Demo02CategoryDO> {
+
+    default List<Demo02CategoryDO> selectList(Demo02CategoryListReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<Demo02CategoryDO>()
+                .likeIfPresent(Demo02CategoryDO::getName, reqVO.getName())
+                .eqIfPresent(Demo02CategoryDO::getParentId, reqVO.getParentId())
+                .betweenIfPresent(Demo02CategoryDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(Demo02CategoryDO::getId));
+    }
+
+	default Demo02CategoryDO selectByParentIdAndName(Long parentId, String name) {
+	    return selectOne(Demo02CategoryDO::getParentId, parentId, Demo02CategoryDO::getName, name);
+	}
+
+    default Long selectCountByParentId(Long parentId) {
+        return selectCount(Demo02CategoryDO::getParentId, parentId);
+    }
+
+}

+ 34 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo03/Demo03CourseMapper.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo.demo03;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 学生课程 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface Demo03CourseMapper extends BaseMapperX<Demo03CourseDO> {
+
+    default PageResult<Demo03CourseDO> selectPage(PageParam reqVO, Long studentId) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<Demo03CourseDO>()
+                .eq(Demo03CourseDO::getStudentId, studentId)
+                .orderByDesc(Demo03CourseDO::getId));
+    }
+
+    default List<Demo03CourseDO> selectListByStudentId(Long studentId) {
+        return selectList(Demo03CourseDO::getStudentId, studentId);
+    }
+
+    default int deleteByStudentId(Long studentId) {
+        return delete(Demo03CourseDO::getStudentId, studentId);
+    }
+
+}

+ 32 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo03/Demo03GradeMapper.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo.demo03;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 学生班级 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface Demo03GradeMapper extends BaseMapperX<Demo03GradeDO> {
+
+    default PageResult<Demo03GradeDO> selectPage(PageParam reqVO, Long studentId) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<Demo03GradeDO>()
+                .eq(Demo03GradeDO::getStudentId, studentId)
+                .orderByDesc(Demo03GradeDO::getId));
+    }
+
+    default Demo03GradeDO selectByStudentId(Long studentId) {
+        return selectOne(Demo03GradeDO::getStudentId, studentId);
+    }
+
+    default int deleteByStudentId(Long studentId) {
+        return delete(Demo03GradeDO::getStudentId, studentId);
+    }
+
+}

+ 27 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/demo03/Demo03StudentMapper.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo.demo03;
+
+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.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo.*;
+
+/**
+ * 学生 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface Demo03StudentMapper extends BaseMapperX<Demo03StudentDO> {
+
+    default PageResult<Demo03StudentDO> selectPage(Demo03StudentPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<Demo03StudentDO>()
+                .likeIfPresent(Demo03StudentDO::getName, reqVO.getName())
+                .eqIfPresent(Demo03StudentDO::getSex, reqVO.getSex())
+                .eqIfPresent(Demo03StudentDO::getDescription, reqVO.getDescription())
+                .betweenIfPresent(Demo03StudentDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(Demo03StudentDO::getId));
+    }
+
+}

+ 0 - 45
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/test/TestDemoMapper.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.module.infra.dal.mysql.test;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExportReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoPageReqVO;
-import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
-
-/**
- * 字典类型 Mapper
- *
- * @author 芋道源码
- */
-@Mapper
-public interface TestDemoMapper extends BaseMapperX<TestDemoDO> {
-
-    default PageResult<TestDemoDO> selectPage(TestDemoPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<TestDemoDO>()
-                .likeIfPresent(TestDemoDO::getName, reqVO.getName())
-                .eqIfPresent(TestDemoDO::getStatus, reqVO.getStatus())
-                .eqIfPresent(TestDemoDO::getType, reqVO.getType())
-                .eqIfPresent(TestDemoDO::getCategory, reqVO.getCategory())
-                .eqIfPresent(TestDemoDO::getRemark, reqVO.getRemark())
-                .betweenIfPresent(TestDemoDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(TestDemoDO::getId));
-    }
-
-    default List<TestDemoDO> selectList(TestDemoExportReqVO reqVO) {
-        return selectList(new LambdaQueryWrapperX<TestDemoDO>()
-                .likeIfPresent(TestDemoDO::getName, reqVO.getName())
-                .eqIfPresent(TestDemoDO::getStatus, reqVO.getStatus())
-                .eqIfPresent(TestDemoDO::getType, reqVO.getType())
-                .eqIfPresent(TestDemoDO::getCategory, reqVO.getCategory())
-                .eqIfPresent(TestDemoDO::getRemark, reqVO.getRemark())
-                .betweenIfPresent(TestDemoDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(TestDemoDO::getId));
-    }
-
-    List<TestDemoDO> selectList2();
-
-}

+ 2 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenColumnHtmlTypeEnum.java

@@ -16,8 +16,8 @@ public enum CodegenColumnHtmlTypeEnum {
     RADIO("radio"), // 单选框
     CHECKBOX("checkbox"), // 复选框
     DATETIME("datetime"), // 日期控件
-    UPLOAD_IMAGE("upload_image"), // 上传图片
-    UPLOAD_FILE("upload_file"), // 上传文件
+    IMAGE_UPLOAD("imageUpload"), // 上传图片
+    FILE_UPLOAD("fileUpload"), // 上传文件
     EDITOR("editor"), // 富文本控件
     ;
 

+ 30 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenTemplateTypeEnum.java

@@ -1,8 +1,11 @@
 package cn.iocoder.yudao.module.infra.enums.codegen;
 
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+import java.util.Objects;
+
 /**
  * 代码生成模板类型
  *
@@ -12,8 +15,13 @@ import lombok.Getter;
 @Getter
 public enum CodegenTemplateTypeEnum {
 
-    CRUD(1), // 单表(增删改查)
+    ONE(1), // 单表(增删改查)
     TREE(2), // 树表(增删改查)
+
+    MASTER_NORMAL(10), // 主子表 - 主表 - 普通模式
+    MASTER_ERP(11), // 主子表 - 主表 - ERP 模式
+    MASTER_INNER(12), // 主子表 - 主表 - 内嵌模式
+    SUB(15), // 主子表 - 子表
     ;
 
     /**
@@ -21,4 +29,25 @@ public enum CodegenTemplateTypeEnum {
      */
     private final Integer type;
 
+    /**
+     * 是否为主表
+     *
+     * @param type 类型
+     * @return 是否主表
+     */
+    public static boolean isMaster(Integer type) {
+        return ObjectUtils.equalsAny(type,
+                MASTER_NORMAL.type, MASTER_ERP.type, MASTER_INNER.type);
+    }
+
+    /**
+     * 是否为树表
+     *
+     * @param type 类型
+     * @return 是否树表
+     */
+    public static boolean isTree(Integer type) {
+        return Objects.equals(type, TREE.type);
+    }
+
 }

+ 8 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenService.java

@@ -48,6 +48,14 @@ public interface CodegenService {
      */
     void deleteCodegen(Long tableId);
 
+    /**
+     * 获得表定义列表
+     *
+     * @param dataSourceConfigId 数据源配置的编号
+     * @return 表定义列表
+     */
+    List<CodegenTableDO> getCodegenTableList(Long dataSourceConfigId);
+
     /**
      * 获得表定义分页
      *

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

@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
 import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
 import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
 import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
 import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
 import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
 import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
@@ -25,10 +26,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.function.BiPredicate;
 import java.util.stream.Collectors;
 
@@ -129,6 +127,16 @@ public class CodegenServiceImpl implements CodegenService {
         if (codegenTableMapper.selectById(updateReqVO.getTable().getId()) == null) {
             throw exception(CODEGEN_TABLE_NOT_EXISTS);
         }
+        // 校验主表字段存在
+        if (Objects.equals(updateReqVO.getTable().getTemplateType(), CodegenTemplateTypeEnum.SUB.getType())) {
+            if (codegenTableMapper.selectById(updateReqVO.getTable().getMasterTableId()) == null) {
+                throw exception(CODEGEN_MASTER_TABLE_NOT_EXISTS, updateReqVO.getTable().getMasterTableId());
+            }
+            if (CollUtil.findOne(updateReqVO.getColumns(),  // 关联主表的字段不存在
+                    column -> column.getId().equals(updateReqVO.getTable().getSubJoinColumnId())) == null) {
+                throw exception(CODEGEN_SUB_COLUMN_NOT_EXISTS, updateReqVO.getTable().getSubJoinColumnId());
+            }
+        }
 
         // 更新 table 表定义
         CodegenTableDO updateTableObj = CodegenConvert.INSTANCE.convert(updateReqVO.getTable());
@@ -208,6 +216,11 @@ public class CodegenServiceImpl implements CodegenService {
         codegenColumnMapper.deleteListByTableId(tableId);
     }
 
+    @Override
+    public List<CodegenTableDO> getCodegenTableList(Long dataSourceConfigId) {
+        return codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId);
+    }
+
     @Override
     public PageResult<CodegenTableDO> getCodegenTablePage(CodegenTablePageReqVO pageReqVO) {
         return codegenTableMapper.selectPage(pageReqVO);
@@ -235,14 +248,34 @@ public class CodegenServiceImpl implements CodegenService {
             throw exception(CODEGEN_COLUMN_NOT_EXISTS);
         }
 
+        // 如果是主子表,则加载对应的子表信息
+        List<CodegenTableDO> subTables = null;
+        List<List<CodegenColumnDO>> subColumnsList = null;
+        if (CodegenTemplateTypeEnum.isMaster(table.getTemplateType())) {
+            // 校验子表存在
+            subTables = codegenTableMapper.selectListByTemplateTypeAndMasterTableId(
+                    CodegenTemplateTypeEnum.SUB.getType(), tableId);
+            if (CollUtil.isEmpty(subTables)) {
+                throw exception(CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE);
+            }
+            // 校验子表的关联字段存在
+            subColumnsList = new ArrayList<>();
+            for (CodegenTableDO subTable : subTables) {
+                List<CodegenColumnDO> subColumns = codegenColumnMapper.selectListByTableId(subTable.getId());
+                if (CollUtil.findOne(subColumns, column -> column.getId().equals(subTable.getSubJoinColumnId())) == null) {
+                    throw exception(CODEGEN_SUB_COLUMN_NOT_EXISTS, subTable.getId());
+                }
+                subColumnsList.add(subColumns);
+            }
+        }
+
         // 执行生成
-        return codegenEngine.execute(table, columns);
+        return codegenEngine.execute(table, columns, subTables, subColumnsList);
     }
 
     @Override
     public List<DatabaseTableRespVO> getDatabaseTableList(Long dataSourceConfigId, String name, String comment) {
         List<TableInfo> tables = databaseTableService.getTableList(dataSourceConfigId, name, comment);
-        // 移除已经生成的表
         // 移除在 Codegen 中,已经存在的
         Set<String> existsTables = CollectionUtils.convertSet(
                 codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId), CodegenTableDO::getTableName);

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

@@ -15,6 +15,7 @@ import com.baomidou.mybatisplus.generator.config.po.TableInfo;
 import com.google.common.collect.Sets;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDateTime;
 import java.util.*;
 
 import static cn.hutool.core.text.CharSequenceUtil.*;
@@ -49,8 +50,8 @@ public class CodegenBuilder {
                     .put("status", CodegenColumnHtmlTypeEnum.RADIO)
                     .put("sex", CodegenColumnHtmlTypeEnum.RADIO)
                     .put("type", CodegenColumnHtmlTypeEnum.SELECT)
-                    .put("image", CodegenColumnHtmlTypeEnum.UPLOAD_IMAGE)
-                    .put("file", CodegenColumnHtmlTypeEnum.UPLOAD_FILE)
+                    .put("image", CodegenColumnHtmlTypeEnum.IMAGE_UPLOAD)
+                    .put("file", CodegenColumnHtmlTypeEnum.FILE_UPLOAD)
                     .put("content", CodegenColumnHtmlTypeEnum.EDITOR)
                     .put("description", CodegenColumnHtmlTypeEnum.EDITOR)
                     .put("demo", CodegenColumnHtmlTypeEnum.EDITOR)
@@ -118,7 +119,7 @@ public class CodegenBuilder {
         table.setClassName(upperFirst(toCamelCase(subAfter(tableName, '_', false))));
         // 去除结尾的表,作为类描述
         table.setClassComment(StrUtil.removeSuffixIgnoreCase(table.getTableComment(), "表"));
-        table.setTemplateType(CodegenTemplateTypeEnum.CRUD.getType());
+        table.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
     }
 
     public List<CodegenColumnDO> buildColumns(Long tableId, List<TableField> tableFields) {
@@ -127,6 +128,10 @@ public class CodegenBuilder {
         for (CodegenColumnDO column : columns) {
             column.setTableId(tableId);
             column.setOrdinalPosition(index++);
+            // 特殊处理:Byte => Integer
+            if (Byte.class.getSimpleName().equals(column.getJavaType())) {
+                column.setJavaType(Integer.class.getSimpleName());
+            }
             // 初始化 Column 列的默认字段
             processColumnOperation(column); // 处理 CRUD 相关的字段的默认值
             processColumnUI(column); // 处理 UI 相关的字段的默认值
@@ -162,10 +167,13 @@ public class CodegenBuilder {
                 .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))
                 .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType()));
         // 如果是 Boolean 类型时,设置为 radio 类型.
-        // 其它类型,因为字段名可以相对保障,所以不进行处理。例如说 date 对应 datetime 类型.
         if (Boolean.class.getSimpleName().equals(column.getJavaType())) {
             column.setHtmlType(CodegenColumnHtmlTypeEnum.RADIO.getType());
         }
+        // 如果是 LocalDateTime 类型,则设置为 datetime 类型
+        if (LocalDateTime.class.getSimpleName().equals(column.getJavaType())) {
+            column.setHtmlType(CodegenColumnHtmlTypeEnum.DATETIME.getType());
+        }
         // 兜底,设置默认为 input 类型
         if (column.getHtmlType() == null) {
             column.setHtmlType(CodegenColumnHtmlTypeEnum.INPUT.getType());

+ 223 - 36
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.infra.service.codegen.inner;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.template.TemplateConfig;
 import cn.hutool.extra.template.TemplateEngine;
@@ -12,7 +14,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
@@ -25,7 +29,9 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
 import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
 import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
 import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableTable;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Table;
@@ -33,10 +39,7 @@ import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import static cn.hutool.core.map.MapUtil.getStr;
 import static cn.hutool.core.text.CharSequenceUtil.*;
@@ -60,20 +63,19 @@ public class CodegenEngine {
      */
     private static final Map<String, String> SERVER_TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序
             // Java module-biz Main
-            .put(javaTemplatePath("controller/vo/baseVO"), javaModuleImplVOFilePath("BaseVO"))
-            .put(javaTemplatePath("controller/vo/createReqVO"), javaModuleImplVOFilePath("CreateReqVO"))
             .put(javaTemplatePath("controller/vo/pageReqVO"), javaModuleImplVOFilePath("PageReqVO"))
+            .put(javaTemplatePath("controller/vo/listReqVO"), javaModuleImplVOFilePath("ListReqVO"))
             .put(javaTemplatePath("controller/vo/respVO"), javaModuleImplVOFilePath("RespVO"))
-            .put(javaTemplatePath("controller/vo/updateReqVO"), javaModuleImplVOFilePath("UpdateReqVO"))
-            .put(javaTemplatePath("controller/vo/exportReqVO"), javaModuleImplVOFilePath("ExportReqVO"))
-            .put(javaTemplatePath("controller/vo/excelVO"), javaModuleImplVOFilePath("ExcelVO"))
+            .put(javaTemplatePath("controller/vo/saveReqVO"), javaModuleImplVOFilePath("SaveReqVO"))
             .put(javaTemplatePath("controller/controller"), javaModuleImplControllerFilePath())
-            .put(javaTemplatePath("convert/convert"),
-                    javaModuleImplMainFilePath("convert/${table.businessName}/${table.className}Convert"))
             .put(javaTemplatePath("dal/do"),
                     javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${table.className}DO"))
+            .put(javaTemplatePath("dal/do_sub"), // 特殊:主子表专属逻辑
+                    javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${subTable.className}DO"))
             .put(javaTemplatePath("dal/mapper"),
                     javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${table.className}Mapper"))
+            .put(javaTemplatePath("dal/mapper_sub"), // 特殊:主子表专属逻辑
+                    javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${subTable.className}Mapper"))
             .put(javaTemplatePath("dal/mapper.xml"), mapperXmlFilePath())
             .put(javaTemplatePath("service/serviceImpl"),
                     javaModuleImplMainFilePath("service/${table.businessName}/${table.className}ServiceImpl"))
@@ -99,34 +101,44 @@ public class CodegenEngine {
     private static final Table<Integer, String, String> FRONT_TEMPLATES = ImmutableTable.<Integer, String, String>builder()
             // Vue2 标准模版
             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/index.vue"),
-                    vueFilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
+                    vueFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"),
                     vueFilePath("api/${table.moduleName}/${classNameVar}.js"))
             // Vue3 标准模版
             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"),
-                    vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/form.vue"),
-                    vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Form.vue"))
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
+            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("api/api.ts"),
-                    vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
+                    vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
             // Vue3 Schema 模版
             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/data.ts"),
-                    vue3FilePath("views/${table.moduleName}/${classNameVar}/${classNameVar}.data.ts"))
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/index.vue"),
-                    vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/form.vue"),
-                    vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Form.vue"))
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("api/api.ts"),
-                    vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
+                    vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
             // Vue3 vben 模版
             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/data.ts"),
-                    vue3FilePath("views/${table.moduleName}/${classNameVar}/${classNameVar}.data.ts"))
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/index.vue"),
-                    vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/form.vue"),
-                    vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Modal.vue"))
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Modal.vue"))
             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("api/api.ts"),
-                    vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
+                    vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
             .build();
 
     @Resource
@@ -149,7 +161,8 @@ public class CodegenEngine {
     }
 
     @PostConstruct
-    private void initGlobalBindingMap() {
+    @VisibleForTesting
+    void initGlobalBindingMap() {
         // 全局配置
         globalBindingMap.put("basePackage", codegenProperties.getBasePackage());
         globalBindingMap.put("baseFrameworkPackage", codegenProperties.getBasePackage()
@@ -174,9 +187,122 @@ public class CodegenEngine {
         globalBindingMap.put("DictConvertClassName", DictConvert.class.getName());
         globalBindingMap.put("OperateLogClassName", OperateLog.class.getName());
         globalBindingMap.put("OperateTypeEnumClassName", OperateTypeEnum.class.getName());
+        globalBindingMap.put("BeanUtils", BeanUtils.class.getName());
     }
 
-    public Map<String, String> execute(CodegenTableDO table, List<CodegenColumnDO> columns) {
+    /**
+     * 生成代码
+     *
+     * @param table 表定义
+     * @param columns table 的字段定义数组
+     * @param subTables 子表数组,当且仅当主子表时使用
+     * @param subColumnsList subTables 的字段定义数组
+     * @return 生成的代码,key 是路径,value 是对应代码
+     */
+    public Map<String, String> execute(CodegenTableDO table, List<CodegenColumnDO> columns,
+                                       List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {
+        // 1.1 初始化 bindMap 上下文
+        Map<String, Object> bindingMap = initBindingMap(table, columns, subTables, subColumnsList);
+        // 1.2 获得模版
+        Map<String, String> templates = getTemplates(table.getFrontType());
+
+        // 2. 执行生成
+        Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
+        templates.forEach((vmPath, filePath) -> {
+            // 2.1 特殊:主子表专属逻辑
+            if (isSubTemplate(vmPath)) {
+                generateSubCode(table, subTables, result, vmPath, filePath, bindingMap);
+                return;
+                // 2.2 特殊:树表专属逻辑
+            } else if (isPageReqVOTemplate(vmPath)) {
+                // 减少多余的类生成,例如说 PageVO.java 类
+                if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
+                    return;
+                }
+            } else if (isListReqVOTemplate(vmPath)) {
+                // 减少多余的类生成,例如说 ListVO.java 类
+                if (!CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
+                    return;
+                }
+            }
+            // 2.3 默认生成
+            generateCode(result, vmPath, filePath, bindingMap);
+        });
+        return result;
+    }
+
+    private void generateCode(Map<String, String> result, String vmPath,
+                              String filePath, Map<String, Object> bindingMap) {
+        filePath = formatFilePath(filePath, bindingMap);
+        String content = templateEngine.getTemplate(vmPath).render(bindingMap);
+        // 格式化代码
+        content = prettyCode(content);
+        result.put(filePath, content);
+    }
+
+    private void generateSubCode(CodegenTableDO table, List<CodegenTableDO> subTables,
+                                 Map<String, String> result, String vmPath,
+                                 String filePath, Map<String, Object> bindingMap) {
+        // 没有子表,所以不生成
+        if (CollUtil.isEmpty(subTables)) {
+            return;
+        }
+        // 主子表的模式匹配。目的:过滤掉个性化的模版
+        if (vmPath.contains("_normal")
+                && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_NORMAL.getType())) {
+            return;
+        }
+        if (vmPath.contains("_erp")
+                && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_ERP.getType())) {
+            return;
+        }
+        if (vmPath.contains("_inner")
+                && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_INNER.getType())) {
+            return;
+        }
+
+        // 逐个生成
+        for (int i = 0; i < subTables.size(); i++) {
+            bindingMap.put("subIndex", i);
+            generateCode(result, vmPath, filePath, bindingMap);
+        }
+        bindingMap.remove("subIndex");
+    }
+
+    /**
+     * 格式化生成后的代码
+     *
+     * 因为尽量让 vm 模版简单,所以统一的处理都在这个方法。
+     * 如果不处理,Vue 的 Pretty 格式校验可能会报错
+     *
+     * @param content 格式化前的代码
+     * @return 格式化后的代码
+     */
+    private String prettyCode(String content) {
+        // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错
+        content = content.replaceAll(",\n}", "\n}").replaceAll(",\n  }", "\n  }");
+        // Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到
+        if (StrUtil.count(content, "dateFormatter") == 1) {
+            content = StrUtils.removeLineContains(content, "dateFormatter");
+        }
+        // Vue 界面:去除多的 dict 相关,只有一个的情况下,说明没使用到
+        if (StrUtil.count(content, "getIntDictOptions") == 1) {
+            content = content.replace("getIntDictOptions, ", "");
+        }
+        if (StrUtil.count(content, "getStrDictOptions") == 1) {
+            content = content.replace("getStrDictOptions, ", "");
+        }
+        if (StrUtil.count(content, "getBoolDictOptions") == 1) {
+            content = content.replace("getBoolDictOptions, ", "");
+        }
+        if (StrUtil.count(content, "DICT_TYPE.") == 0) {
+            content = StrUtils.removeLineContains(content, "DICT_TYPE");
+        }
+        return content;
+    }
+
+    private Map<String, Object> initBindingMap(CodegenTableDO table, List<CodegenColumnDO> columns,
+                                               List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {
         // 创建 bindingMap
         Map<String, Object> bindingMap = new HashMap<>(globalBindingMap);
         bindingMap.put("table", table);
@@ -196,17 +322,54 @@ public class CodegenEngine {
         // permission 前缀
         bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase);
 
-        // 执行生成
-        Map<String, String> templates = getTemplates(table.getFrontType());
-        Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
-        templates.forEach((vmPath, filePath) -> {
-            filePath = formatFilePath(filePath, bindingMap);
-            String content = templateEngine.getTemplate(vmPath).render(bindingMap);
-            // 去除字段后面多余的 , 逗号
-            content = content.replaceAll(",\n}", "\n}").replaceAll(",\n  }", "\n  }");
-            result.put(filePath, content);
-        });
-        return result;
+        // 特殊:树表专属逻辑
+        if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
+            CodegenColumnDO treeParentColumn = CollUtil.findOne(columns,
+                    column -> Objects.equals(column.getId(), table.getTreeParentColumnId()));
+            bindingMap.put("treeParentColumn", treeParentColumn);
+            bindingMap.put("treeParentColumn_javaField_underlineCase", toUnderlineCase(treeParentColumn.getJavaField()));
+            CodegenColumnDO treeNameColumn = CollUtil.findOne(columns,
+                    column -> Objects.equals(column.getId(), table.getTreeNameColumnId()));
+            bindingMap.put("treeNameColumn", treeNameColumn);
+            bindingMap.put("treeNameColumn_javaField_underlineCase", toUnderlineCase(treeNameColumn.getJavaField()));
+        }
+
+        // 特殊:主子表专属逻辑
+        if (CollUtil.isNotEmpty(subTables)) {
+            // 创建 bindingMap
+            bindingMap.put("subTables", subTables);
+            bindingMap.put("subColumnsList", subColumnsList);
+            List<CodegenColumnDO> subPrimaryColumns = new ArrayList<>();
+            List<CodegenColumnDO> subJoinColumns = new ArrayList<>();
+            List<String> subJoinColumnStrikeCases = new ArrayList<>();
+            List<String> subSimpleClassNames = new ArrayList<>();
+            List<String> subClassNameVars = new ArrayList<>();
+            List<String> simpleClassNameUnderlineCases = new ArrayList<>();
+            List<String> subSimpleClassNameStrikeCases = new ArrayList<>();
+            for (int i = 0; i < subTables.size(); i++) {
+                CodegenTableDO subTable = subTables.get(i);
+                List<CodegenColumnDO> subColumns = subColumnsList.get(i);
+                subPrimaryColumns.add(CollectionUtils.findFirst(subColumns, CodegenColumnDO::getPrimaryKey)); //
+                CodegenColumnDO subColumn = CollectionUtils.findFirst(subColumns, // 关联的字段
+                        column -> Objects.equals(column.getId(), subTable.getSubJoinColumnId()));
+                subJoinColumns.add(subColumn);
+                subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type
+                // className 相关
+                String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
+                subSimpleClassNames.add(subSimpleClassName);
+                simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type
+                subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量
+                subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type
+            }
+            bindingMap.put("subPrimaryColumns", subPrimaryColumns);
+            bindingMap.put("subJoinColumns", subJoinColumns);
+            bindingMap.put("subJoinColumn_strikeCases", subJoinColumnStrikeCases);
+            bindingMap.put("subSimpleClassNames", subSimpleClassNames);
+            bindingMap.put("simpleClassNameUnderlineCases", simpleClassNameUnderlineCases);
+            bindingMap.put("subClassNameVars", subClassNameVars);
+            bindingMap.put("subSimpleClassName_strikeCases", subSimpleClassNameStrikeCases);
+        }
+        return bindingMap;
     }
 
     private Map<String, String> getTemplates(Integer frontType) {
@@ -216,6 +379,7 @@ public class CodegenEngine {
         return templates;
     }
 
+    @SuppressWarnings("unchecked")
     private String formatFilePath(String filePath, Map<String, Object> bindingMap) {
         filePath = StrUtil.replace(filePath, "${basePackage}",
                 getStr(bindingMap, "basePackage").replaceAll("\\.", "/"));
@@ -232,6 +396,16 @@ public class CodegenEngine {
         filePath = StrUtil.replace(filePath, "${table.moduleName}", table.getModuleName());
         filePath = StrUtil.replace(filePath, "${table.businessName}", table.getBusinessName());
         filePath = StrUtil.replace(filePath, "${table.className}", table.getClassName());
+        // 特殊:主子表专属逻辑
+        Integer subIndex = (Integer) bindingMap.get("subIndex");
+        if (subIndex != null) {
+            CodegenTableDO subTable = ((List<CodegenTableDO>) bindingMap.get("subTables")).get(subIndex);
+            filePath = StrUtil.replace(filePath, "${subTable.moduleName}", subTable.getModuleName());
+            filePath = StrUtil.replace(filePath, "${subTable.businessName}", subTable.getBusinessName());
+            filePath = StrUtil.replace(filePath, "${subTable.className}", subTable.getClassName());
+            filePath = StrUtil.replace(filePath, "${subSimpleClassName}",
+                    ((List<String>) bindingMap.get("subSimpleClassNames")).get(subIndex));
+        }
         return filePath;
     }
 
@@ -298,4 +472,17 @@ public class CodegenEngine {
     private static String vue3VbenTemplatePath(String path) {
         return "codegen/vue3_vben/" + path + ".vm";
     }
+
+    private static boolean isSubTemplate(String path) {
+        return path.contains("_sub");
+    }
+
+    private static boolean isPageReqVOTemplate(String path) {
+        return path.contains("pageReqVO");
+    }
+
+    private static boolean isListReqVOTemplate(String path) {
+        return path.contains("listReqVO");
+    }
+
 }

+ 55 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo01/Demo01ContactService.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.infra.service.demo.demo01;
+
+import javax.validation.*;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo.Demo01ContactSaveReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+/**
+ * 示例联系人 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface Demo01ContactService {
+
+    /**
+     * 创建示例联系人
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createDemo01Contact(@Valid Demo01ContactSaveReqVO createReqVO);
+
+    /**
+     * 更新示例联系人
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateDemo01Contact(@Valid Demo01ContactSaveReqVO updateReqVO);
+
+    /**
+     * 删除示例联系人
+     *
+     * @param id 编号
+     */
+    void deleteDemo01Contact(Long id);
+
+    /**
+     * 获得示例联系人
+     *
+     * @param id 编号
+     * @return 示例联系人
+     */
+    Demo01ContactDO getDemo01Contact(Long id);
+
+    /**
+     * 获得示例联系人分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 示例联系人分页
+     */
+    PageResult<Demo01ContactDO> getDemo01ContactPage(Demo01ContactPageReqVO pageReqVO);
+
+}

+ 72 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo01/Demo01ContactServiceImpl.java

@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.infra.service.demo.demo01;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo01.vo.Demo01ContactSaveReqVO;
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.demo01.Demo01ContactMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+/**
+ * 示例联系人 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class Demo01ContactServiceImpl implements Demo01ContactService {
+
+    @Resource
+    private Demo01ContactMapper demo01ContactMapper;
+
+    @Override
+    public Long createDemo01Contact(Demo01ContactSaveReqVO createReqVO) {
+        // 插入
+        Demo01ContactDO demo01Contact = BeanUtils.toBean(createReqVO, Demo01ContactDO.class);
+        demo01ContactMapper.insert(demo01Contact);
+        // 返回
+        return demo01Contact.getId();
+    }
+
+    @Override
+    public void updateDemo01Contact(Demo01ContactSaveReqVO updateReqVO) {
+        // 校验存在
+        validateDemo01ContactExists(updateReqVO.getId());
+        // 更新
+        Demo01ContactDO updateObj = BeanUtils.toBean(updateReqVO, Demo01ContactDO.class);
+        demo01ContactMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteDemo01Contact(Long id) {
+        // 校验存在
+        validateDemo01ContactExists(id);
+        // 删除
+        demo01ContactMapper.deleteById(id);
+    }
+
+    private void validateDemo01ContactExists(Long id) {
+        if (demo01ContactMapper.selectById(id) == null) {
+            throw exception(DEMO01_CONTACT_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public Demo01ContactDO getDemo01Contact(Long id) {
+        return demo01ContactMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<Demo01ContactDO> getDemo01ContactPage(Demo01ContactPageReqVO pageReqVO) {
+        return demo01ContactMapper.selectPage(pageReqVO);
+    }
+
+}

+ 55 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo02/Demo02CategoryService.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.infra.service.demo.demo02;
+
+import java.util.*;
+import javax.validation.*;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo.Demo02CategorySaveReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;
+
+/**
+ * 示例分类 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface Demo02CategoryService {
+
+    /**
+     * 创建示例分类
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createDemo02Category(@Valid Demo02CategorySaveReqVO createReqVO);
+
+    /**
+     * 更新示例分类
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateDemo02Category(@Valid Demo02CategorySaveReqVO updateReqVO);
+
+    /**
+     * 删除示例分类
+     *
+     * @param id 编号
+     */
+    void deleteDemo02Category(Long id);
+
+    /**
+     * 获得示例分类
+     *
+     * @param id 编号
+     * @return 示例分类
+     */
+    Demo02CategoryDO getDemo02Category(Long id);
+
+    /**
+     * 获得示例分类列表
+     *
+     * @param listReqVO 查询条件
+     * @return 示例分类列表
+     */
+    List<Demo02CategoryDO> getDemo02CategoryList(Demo02CategoryListReqVO listReqVO);
+
+}

+ 134 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo02/Demo02CategoryServiceImpl.java

@@ -0,0 +1,134 @@
+package cn.iocoder.yudao.module.infra.service.demo.demo02;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo02.vo.Demo02CategorySaveReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.demo02.Demo02CategoryMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+/**
+ * 示例分类 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class Demo02CategoryServiceImpl implements Demo02CategoryService {
+
+    @Resource
+    private Demo02CategoryMapper demo02CategoryMapper;
+
+    @Override
+    public Long createDemo02Category(Demo02CategorySaveReqVO createReqVO) {
+        // 校验父级编号的有效性
+        validateParentDemo02Category(null, createReqVO.getParentId());
+        // 校验名字的唯一性
+        validateDemo02CategoryNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
+
+        // 插入
+        Demo02CategoryDO demo02Category = BeanUtils.toBean(createReqVO, Demo02CategoryDO.class);
+        demo02CategoryMapper.insert(demo02Category);
+        // 返回
+        return demo02Category.getId();
+    }
+
+    @Override
+    public void updateDemo02Category(Demo02CategorySaveReqVO updateReqVO) {
+        // 校验存在
+        validateDemo02CategoryExists(updateReqVO.getId());
+        // 校验父级编号的有效性
+        validateParentDemo02Category(updateReqVO.getId(), updateReqVO.getParentId());
+        // 校验名字的唯一性
+        validateDemo02CategoryNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
+
+        // 更新
+        Demo02CategoryDO updateObj = BeanUtils.toBean(updateReqVO, Demo02CategoryDO.class);
+        demo02CategoryMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteDemo02Category(Long id) {
+        // 校验存在
+        validateDemo02CategoryExists(id);
+        // 校验是否有子示例分类
+        if (demo02CategoryMapper.selectCountByParentId(id) > 0) {
+            throw exception(DEMO02_CATEGORY_EXITS_CHILDREN);
+        }
+        // 删除
+        demo02CategoryMapper.deleteById(id);
+    }
+
+    private void validateDemo02CategoryExists(Long id) {
+        if (demo02CategoryMapper.selectById(id) == null) {
+            throw exception(DEMO02_CATEGORY_NOT_EXISTS);
+        }
+    }
+
+    private void validateParentDemo02Category(Long id, Long parentId) {
+        if (parentId == null || Demo02CategoryDO.PARENT_ID_ROOT.equals(parentId)) {
+            return;
+        }
+        // 1. 不能设置自己为父示例分类
+        if (Objects.equals(id, parentId)) {
+            throw exception(DEMO02_CATEGORY_PARENT_ERROR);
+        }
+        // 2. 父示例分类不存在
+        Demo02CategoryDO parentDemo02Category = demo02CategoryMapper.selectById(parentId);
+        if (parentDemo02Category == null) {
+            throw exception(DEMO02_CATEGORY_PARENT_NOT_EXITS);
+        }
+        // 3. 递归校验父示例分类,如果父示例分类是自己的子示例分类,则报错,避免形成环路
+        if (id == null) { // id 为空,说明新增,不需要考虑环路
+            return;
+        }
+        for (int i = 0; i < Short.MAX_VALUE; i++) {
+            // 3.1 校验环路
+            parentId = parentDemo02Category.getParentId();
+            if (Objects.equals(id, parentId)) {
+                throw exception(DEMO02_CATEGORY_PARENT_IS_CHILD);
+            }
+            // 3.2 继续递归下一级父示例分类
+            if (parentId == null || Demo02CategoryDO.PARENT_ID_ROOT.equals(parentId)) {
+                break;
+            }
+            parentDemo02Category = demo02CategoryMapper.selectById(parentId);
+            if (parentDemo02Category == null) {
+                break;
+            }
+        }
+    }
+
+    private void validateDemo02CategoryNameUnique(Long id, Long parentId, String name) {
+        Demo02CategoryDO demo02Category = demo02CategoryMapper.selectByParentIdAndName(parentId, name);
+        if (demo02Category == null) {
+            return;
+        }
+        // 如果 id 为空,说明不用比较是否为相同 id 的示例分类
+        if (id == null) {
+            throw exception(DEMO02_CATEGORY_NAME_DUPLICATE);
+        }
+        if (!Objects.equals(demo02Category.getId(), id)) {
+            throw exception(DEMO02_CATEGORY_NAME_DUPLICATE);
+        }
+    }
+
+    @Override
+    public Demo02CategoryDO getDemo02Category(Long id) {
+        return demo02CategoryMapper.selectById(id);
+    }
+
+    @Override
+    public List<Demo02CategoryDO> getDemo02CategoryList(Demo02CategoryListReqVO listReqVO) {
+        return demo02CategoryMapper.selectList(listReqVO);
+    }
+
+}

+ 158 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/Demo03StudentService.java

@@ -0,0 +1,158 @@
+package cn.iocoder.yudao.module.infra.service.demo.demo03;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo.Demo03StudentPageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo.Demo03StudentSaveReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 学生 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface Demo03StudentService {
+
+    /**
+     * 创建学生
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createDemo03Student(@Valid Demo03StudentSaveReqVO createReqVO);
+
+    /**
+     * 更新学生
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateDemo03Student(@Valid Demo03StudentSaveReqVO updateReqVO);
+
+    /**
+     * 删除学生
+     *
+     * @param id 编号
+     */
+    void deleteDemo03Student(Long id);
+
+    /**
+     * 获得学生
+     *
+     * @param id 编号
+     * @return 学生
+     */
+    Demo03StudentDO getDemo03Student(Long id);
+
+    /**
+     * 获得学生分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 学生分页
+     */
+    PageResult<Demo03StudentDO> getDemo03StudentPage(Demo03StudentPageReqVO pageReqVO);
+
+
+    // ==================== 子表(学生课程) ====================
+
+    /**
+     * 获得学生课程列表
+     *
+     * @param studentId 学生编号
+     * @return 学生课程列表
+     */
+    List<Demo03CourseDO> getDemo03CourseListByStudentId(Long studentId);
+
+    /**
+     * 获得学生课程分页
+     *
+     * @param pageReqVO 分页查询
+     * @param studentId 学生编号
+     * @return 学生课程分页
+     */
+    PageResult<Demo03CourseDO> getDemo03CoursePage(PageParam pageReqVO, Long studentId);
+
+    /**
+     * 创建学生课程
+     *
+     * @param demo03Course 创建信息
+     * @return 编号
+     */
+    Long createDemo03Course(@Valid Demo03CourseDO demo03Course);
+
+    /**
+     * 更新学生课程
+     *
+     * @param demo03Course 更新信息
+     */
+    void updateDemo03Course(@Valid Demo03CourseDO demo03Course);
+
+    /**
+     * 删除学生课程
+     *
+     * @param id 编号
+     */
+    void deleteDemo03Course(Long id);
+
+    /**
+     * 获得学生课程
+     *
+     * @param id 编号
+     * @return 学生课程
+     */
+    Demo03CourseDO getDemo03Course(Long id);
+
+    // ==================== 子表(学生班级) ====================
+
+    /**
+     * 获得学生班级
+     *
+     * @param studentId 学生编号
+     * @return 学生班级
+     */
+    Demo03GradeDO getDemo03GradeByStudentId(Long studentId);
+
+    /**
+     * 获得学生班级分页
+     *
+     * @param pageReqVO 分页查询
+     * @param studentId 学生编号
+     * @return 学生班级分页
+     */
+    PageResult<Demo03GradeDO> getDemo03GradePage(PageParam pageReqVO, Long studentId);
+
+    /**
+     * 创建学生班级
+     *
+     * @param demo03Grade 创建信息
+     * @return 编号
+     */
+    Long createDemo03Grade(@Valid Demo03GradeDO demo03Grade);
+
+    /**
+     * 更新学生班级
+     *
+     * @param demo03Grade 更新信息
+     */
+    void updateDemo03Grade(@Valid Demo03GradeDO demo03Grade);
+
+    /**
+     * 删除学生班级
+     *
+     * @param id 编号
+     */
+    void deleteDemo03Grade(Long id);
+
+    /**
+     * 获得学生班级
+     *
+     * @param id 编号
+     * @return 学生班级
+     */
+    Demo03GradeDO getDemo03Grade(Long id);
+
+}

+ 217 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/Demo03StudentServiceImpl.java

@@ -0,0 +1,217 @@
+package cn.iocoder.yudao.module.infra.service.demo.demo03;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo.Demo03StudentPageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.demo03.vo.Demo03StudentSaveReqVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.demo03.Demo03CourseMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.demo03.Demo03GradeMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.demo03.Demo03StudentMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+/**
+ * 学生 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class Demo03StudentServiceImpl implements Demo03StudentService {
+
+    @Resource
+    private Demo03StudentMapper demo03StudentMapper;
+    @Resource
+    private Demo03CourseMapper demo03CourseMapper;
+    @Resource
+    private Demo03GradeMapper demo03GradeMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createDemo03Student(Demo03StudentSaveReqVO createReqVO) {
+        // 插入
+        Demo03StudentDO demo03Student = BeanUtils.toBean(createReqVO, Demo03StudentDO.class);
+        demo03StudentMapper.insert(demo03Student);
+
+        // 插入子表
+        createDemo03CourseList(demo03Student.getId(), createReqVO.getDemo03Courses());
+        createDemo03Grade(demo03Student.getId(), createReqVO.getDemo03Grade());
+        // 返回
+        return demo03Student.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateDemo03Student(Demo03StudentSaveReqVO updateReqVO) {
+        // 校验存在
+        validateDemo03StudentExists(updateReqVO.getId());
+        // 更新
+        Demo03StudentDO updateObj = BeanUtils.toBean(updateReqVO, Demo03StudentDO.class);
+        demo03StudentMapper.updateById(updateObj);
+
+        // 更新子表
+        updateDemo03CourseList(updateReqVO.getId(), updateReqVO.getDemo03Courses());
+        updateDemo03Grade(updateReqVO.getId(), updateReqVO.getDemo03Grade());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteDemo03Student(Long id) {
+        // 校验存在
+        validateDemo03StudentExists(id);
+        // 删除
+        demo03StudentMapper.deleteById(id);
+
+        // 删除子表
+        deleteDemo03CourseByStudentId(id);
+        deleteDemo03GradeByStudentId(id);
+    }
+
+    private void validateDemo03StudentExists(Long id) {
+        if (demo03StudentMapper.selectById(id) == null) {
+            throw exception(DEMO03_STUDENT_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public Demo03StudentDO getDemo03Student(Long id) {
+        return demo03StudentMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<Demo03StudentDO> getDemo03StudentPage(Demo03StudentPageReqVO pageReqVO) {
+        return demo03StudentMapper.selectPage(pageReqVO);
+    }
+
+    // ==================== 子表(学生课程) ====================
+
+    @Override
+    public List<Demo03CourseDO> getDemo03CourseListByStudentId(Long studentId) {
+        return demo03CourseMapper.selectListByStudentId(studentId);
+    }
+
+    private void createDemo03CourseList(Long studentId, List<Demo03CourseDO> list) {
+        if (list != null) {
+            list.forEach(o -> o.setStudentId(studentId));
+        }
+        demo03CourseMapper.insertBatch(list);
+    }
+
+    private void updateDemo03CourseList(Long studentId, List<Demo03CourseDO> list) {
+        deleteDemo03CourseByStudentId(studentId);
+		list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
+        createDemo03CourseList(studentId, list);
+    }
+
+    private void deleteDemo03CourseByStudentId(Long studentId) {
+        demo03CourseMapper.deleteByStudentId(studentId);
+    }
+
+    @Override
+    public PageResult<Demo03CourseDO> getDemo03CoursePage(PageParam pageReqVO, Long studentId) {
+        return demo03CourseMapper.selectPage(pageReqVO, studentId);
+    }
+
+    @Override
+    public Long createDemo03Course(Demo03CourseDO demo03Course) {
+        demo03CourseMapper.insert(demo03Course);
+        return demo03Course.getId();
+    }
+
+    @Override
+    public void updateDemo03Course(Demo03CourseDO demo03Course) {
+        demo03CourseMapper.updateById(demo03Course);
+    }
+
+    @Override
+    public void deleteDemo03Course(Long id) {
+        demo03CourseMapper.deleteById(id);
+    }
+
+    @Override
+    public Demo03CourseDO getDemo03Course(Long id) {
+        return demo03CourseMapper.selectById(id);
+    }
+
+    // ==================== 子表(学生班级) ====================
+
+    @Override
+    public Demo03GradeDO getDemo03GradeByStudentId(Long studentId) {
+        return demo03GradeMapper.selectByStudentId(studentId);
+    }
+
+    private void createDemo03Grade(Long studentId, Demo03GradeDO demo03Grade) {
+        if (demo03Grade == null) {
+            return;
+        }
+        demo03Grade.setStudentId(studentId);
+        demo03GradeMapper.insert(demo03Grade);
+    }
+
+    private void updateDemo03Grade(Long studentId, Demo03GradeDO demo03Grade) {
+        if (demo03Grade == null) {
+			return;
+        }
+        demo03Grade.setStudentId(studentId);
+        demo03Grade.setUpdater(null).setUpdateTime(null); // 解决更新情况下:updateTime 不更新
+        demo03GradeMapper.insertOrUpdate(demo03Grade);
+    }
+
+    private void deleteDemo03GradeByStudentId(Long studentId) {
+        demo03GradeMapper.deleteByStudentId(studentId);
+    }
+
+    @Override
+    public PageResult<Demo03GradeDO> getDemo03GradePage(PageParam pageReqVO, Long studentId) {
+        return demo03GradeMapper.selectPage(pageReqVO, studentId);
+    }
+
+    @Override
+    public Long createDemo03Grade(Demo03GradeDO demo03Grade) {
+        // 校验是否已经存在
+        if (demo03GradeMapper.selectByStudentId(demo03Grade.getStudentId()) != null) {
+            throw exception(DEMO03_GRADE_EXISTS);
+        }
+        demo03GradeMapper.insert(demo03Grade);
+        return demo03Grade.getId();
+    }
+
+    @Override
+    public void updateDemo03Grade(Demo03GradeDO demo03Grade) {
+        // 校验存在
+        validateDemo03GradeExists(demo03Grade.getId());
+        // 更新
+        demo03GradeMapper.updateById(demo03Grade);
+    }
+
+    @Override
+    public void deleteDemo03Grade(Long id) {
+        // 校验存在
+        validateDemo03GradeExists(id);
+        // 删除
+        demo03GradeMapper.deleteById(id);
+    }
+
+    @Override
+    public Demo03GradeDO getDemo03Grade(Long id) {
+        return demo03GradeMapper.selectById(id);
+    }
+
+    private void validateDemo03GradeExists(Long id) {
+        if (demo03GradeMapper.selectById(id) == null) {
+            throw exception(DEMO03_GRADE_NOT_EXISTS);
+        }
+    }
+
+}

+ 0 - 75
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/test/TestDemoService.java

@@ -1,75 +0,0 @@
-package cn.iocoder.yudao.module.infra.service.test;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoCreateReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExportReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoPageReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoUpdateReqVO;
-import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
-
-import javax.validation.Valid;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * 字典类型 Service 接口
- *
- * @author 芋道源码
- */
-public interface TestDemoService {
-
-    /**
-     * 创建字典类型
-     *
-     * @param createReqVO 创建信息
-     * @return 编号
-     */
-    Long createTestDemo(@Valid TestDemoCreateReqVO createReqVO);
-
-    /**
-     * 更新字典类型
-     *
-     * @param updateReqVO 更新信息
-     */
-    void updateTestDemo(@Valid TestDemoUpdateReqVO updateReqVO);
-
-    /**
-     * 删除字典类型
-     *
-     * @param id 编号
-     */
-    void deleteTestDemo(Long id);
-
-    /**
-     * 获得字典类型
-     *
-     * @param id 编号
-     * @return 字典类型
-     */
-    TestDemoDO getTestDemo(Long id);
-
-    /**
-     * 获得字典类型列表
-     *
-     * @param ids 编号
-     * @return 字典类型列表
-     */
-    List<TestDemoDO> getTestDemoList(Collection<Long> ids);
-
-    /**
-     * 获得字典类型分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 字典类型分页
-     */
-    PageResult<TestDemoDO> getTestDemoPage(TestDemoPageReqVO pageReqVO);
-
-    /**
-     * 获得字典类型列表, 用于 Excel 导出
-     *
-     * @param exportReqVO 查询条件
-     * @return 字典类型列表
-     */
-    List<TestDemoDO> getTestDemoList(TestDemoExportReqVO exportReqVO);
-
-}

+ 0 - 91
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/test/TestDemoServiceImpl.java

@@ -1,91 +0,0 @@
-package cn.iocoder.yudao.module.infra.service.test;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoCreateReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoExportReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoPageReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.test.vo.TestDemoUpdateReqVO;
-import cn.iocoder.yudao.module.infra.convert.test.TestDemoConvert;
-import cn.iocoder.yudao.module.infra.dal.dataobject.test.TestDemoDO;
-import cn.iocoder.yudao.module.infra.dal.mysql.test.TestDemoMapper;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.TEST_DEMO_NOT_EXISTS;
-
-/**
- * 字典类型 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-public class TestDemoServiceImpl implements TestDemoService {
-
-    @Resource
-    private TestDemoMapper testDemoMapper;
-
-    @Override
-    public Long createTestDemo(TestDemoCreateReqVO createReqVO) {
-        // 插入
-        TestDemoDO testDemo = TestDemoConvert.INSTANCE.convert(createReqVO);
-        testDemoMapper.insert(testDemo);
-        // 返回
-        return testDemo.getId();
-    }
-
-    @Override
-    @CacheEvict(value = "test", key = "#updateReqVO.id")
-    public void updateTestDemo(TestDemoUpdateReqVO updateReqVO) {
-        // 校验存在
-        validateTestDemoExists(updateReqVO.getId());
-        // 更新
-        TestDemoDO updateObj = TestDemoConvert.INSTANCE.convert(updateReqVO);
-        testDemoMapper.updateById(updateObj);
-    }
-
-    @Override
-    @CacheEvict(value = "test", key = "#id")
-    public void deleteTestDemo(Long id) {
-        // 校验存在
-        validateTestDemoExists(id);
-        // 删除
-        testDemoMapper.deleteById(id);
-    }
-
-    private void validateTestDemoExists(Long id) {
-        if (testDemoMapper.selectById(id) == null) {
-            throw exception(TEST_DEMO_NOT_EXISTS);
-        }
-    }
-
-    @Override
-    @Cacheable(cacheNames = "test", key = "#id")
-    public TestDemoDO getTestDemo(Long id) {
-        return testDemoMapper.selectById(id);
-    }
-
-    @Override
-    public List<TestDemoDO> getTestDemoList(Collection<Long> ids) {
-        return testDemoMapper.selectBatchIds(ids);
-    }
-
-    @Override
-    public PageResult<TestDemoDO> getTestDemoPage(TestDemoPageReqVO pageReqVO) {
-//        testDemoMapper.selectList2();
-        return testDemoMapper.selectPage(pageReqVO);
-    }
-
-    @Override
-    public List<TestDemoDO> getTestDemoList(TestDemoExportReqVO exportReqVO) {
-        return testDemoMapper.selectList(exportReqVO);
-    }
-
-}

+ 155 - 33
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/controller.vm

@@ -15,8 +15,10 @@ import javax.servlet.http.*;
 import java.util.*;
 import java.io.IOException;
 
+import ${PageParamClassName};
 import ${PageResultClassName};
 import ${CommonResultClassName};
+import ${BeanUtils};
 import static ${CommonResultClassName}.success;
 
 import ${ExcelUtilsClassName};
@@ -26,7 +28,10 @@ import static ${OperateTypeEnumClassName}.*;
 
 import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
 import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
-import ${basePackage}.module.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+#end
 import ${basePackage}.module.${table.moduleName}.service.${table.businessName}.${table.className}Service;
 
 @Tag(name = "${sceneEnum.name} - ${table.classComment}")
@@ -41,17 +46,19 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
 
     @PostMapping("/create")
     @Operation(summary = "创建${table.classComment}")
-#if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')")#end
-
-    public CommonResult<${primaryColumn.javaType}> create${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}CreateReqVO createReqVO) {
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')")
+#end
+    public CommonResult<${primaryColumn.javaType}> create${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO) {
         return success(${classNameVar}Service.create${simpleClassName}(createReqVO));
     }
 
     @PutMapping("/update")
     @Operation(summary = "更新${table.classComment}")
-#if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')")#end
-
-    public CommonResult<Boolean> update${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}UpdateReqVO updateReqVO) {
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')")
+#end
+    public CommonResult<Boolean> update${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO) {
         ${classNameVar}Service.update${simpleClassName}(updateReqVO);
         return success(true);
     }
@@ -59,8 +66,9 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
     @DeleteMapping("/delete")
     @Operation(summary = "删除${table.classComment}")
     @Parameter(name = "id", description = "编号", required = true)
-#if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')")#end
-
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')")
+#end
     public CommonResult<Boolean> delete${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
         ${classNameVar}Service.delete${simpleClassName}(id);
         return success(true);
@@ -69,43 +77,157 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
     @GetMapping("/get")
     @Operation(summary = "获得${table.classComment}")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
-#if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
-
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
     public CommonResult<${sceneEnum.prefixClass}${table.className}RespVO> get${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
         ${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id);
-        return success(${table.className}Convert.INSTANCE.convert(${classNameVar}));
-    }
-
-    @GetMapping("/list")
-    @Operation(summary = "获得${table.classComment}列表")
-    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
-#if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
-
-    public CommonResult<List<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}List(@RequestParam("ids") Collection<${primaryColumn.javaType}> ids) {
-        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(ids);
-        return success(${table.className}Convert.INSTANCE.convertList(list));
+        return success(BeanUtils.toBean(${classNameVar}, ${sceneEnum.prefixClass}${table.className}RespVO.class));
     }
 
+#if ( $table.templateType != 2 )
     @GetMapping("/page")
     @Operation(summary = "获得${table.classComment}分页")
-#if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+    public CommonResult<PageResult<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {
+        PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, ${sceneEnum.prefixClass}${table.className}RespVO.class));
+    }
 
-    public CommonResult<PageResult<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageVO) {
-        PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageVO);
-        return success(${table.className}Convert.INSTANCE.convertPage(pageResult));
+## 特殊:树表专属逻辑(树不需要分页接口)
+#else
+    @GetMapping("/list")
+    @Operation(summary = "获得${table.classComment}列表")
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+    public CommonResult<List<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}List(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) {
+        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO);
+        return success(BeanUtils.toBean(list, ${sceneEnum.prefixClass}${table.className}RespVO.class));
     }
 
+#end
     @GetMapping("/export-excel")
     @Operation(summary = "导出${table.classComment} Excel")
-#if ($sceneEnum.scene == 1)    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')")#end
-
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')")
+#end
     @OperateLog(type = EXPORT)
-    public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO,
+#if ( $table.templateType != 2 )
+    public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${table.className}RespVO.class,
+                        BeanUtils.toBean(list, ${table.className}RespVO.class));
+    }
+## 特殊:树表专属逻辑(树不需要分页接口)
+#else
+    public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO,
               HttpServletResponse response) throws IOException {
-        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(exportReqVO);
+        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO);
         // 导出 Excel
-        List<${sceneEnum.prefixClass}${table.className}ExcelVO> datas = ${table.className}Convert.INSTANCE.convertList02(list);
-        ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${sceneEnum.prefixClass}${table.className}ExcelVO.class, datas);
+        ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${table.className}RespVO.class,
+                        BeanUtils.toBean(list, ${table.className}RespVO.class));
+    }
+#end
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+#set ($subClassNameVar = $subClassNameVars.get($index))
+    // ==================== 子表($subTable.classComment) ====================
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+    @GetMapping("/${subSimpleClassName_strikeCase}/page")
+    @Operation(summary = "获得${subTable.classComment}分页")
+    @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}")
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+    public CommonResult<PageResult<${subTable.className}DO>> get${subSimpleClassName}Page(PageParam pageReqVO,
+                                                                                        @RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return success(${classNameVar}Service.get${subSimpleClassName}Page(pageReqVO, ${subJoinColumn.javaField}));
+    }
+
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+    #if ( $subTable.subJoinMany )
+    @GetMapping("/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}")
+    @Operation(summary = "获得${subTable.classComment}列表")
+    @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}")
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+    public CommonResult<List<${subTable.className}DO>> get${subSimpleClassName}ListBy${SubJoinColumnName}(@RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return success(${classNameVar}Service.get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}));
+    }
+
+    #else
+    @GetMapping("/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}")
+    @Operation(summary = "获得${subTable.classComment}")
+    @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}")
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+    public CommonResult<${subTable.className}DO> get${subSimpleClassName}By${SubJoinColumnName}(@RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return success(${classNameVar}Service.get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}));
+    }
+
+    #end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+    @PostMapping("/${subSimpleClassName_strikeCase}/create")
+    @Operation(summary = "创建${subTable.classComment}")
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')")
+#end
+    public CommonResult<${subPrimaryColumn.javaType}> create${subSimpleClassName}(@Valid @RequestBody ${subTable.className}DO ${subClassNameVar}) {
+        return success(${classNameVar}Service.create${subSimpleClassName}(${subClassNameVar}));
+    }
+
+    @PutMapping("/${subSimpleClassName_strikeCase}/update")
+    @Operation(summary = "更新${subTable.classComment}")
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')")
+#end
+    public CommonResult<Boolean> update${subSimpleClassName}(@Valid @RequestBody ${subTable.className}DO ${subClassNameVar}) {
+        ${classNameVar}Service.update${subSimpleClassName}(${subClassNameVar});
+        return success(true);
+    }
+
+    @DeleteMapping("/${subSimpleClassName_strikeCase}/delete")
+    @Parameter(name = "id", description = "编号", required = true)
+    @Operation(summary = "删除${subTable.classComment}")
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')")
+#end
+    public CommonResult<Boolean> delete${subSimpleClassName}(@RequestParam("id") ${subPrimaryColumn.javaType} id) {
+        ${classNameVar}Service.delete${subSimpleClassName}(id);
+        return success(true);
     }
 
-}
+	@GetMapping("/${subSimpleClassName_strikeCase}/get")
+	@Operation(summary = "获得${subTable.classComment}")
+	@Parameter(name = "id", description = "编号", required = true)
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+	public CommonResult<${subTable.className}DO> get${subSimpleClassName}(@RequestParam("id") ${subPrimaryColumn.javaType} id) {
+	    return success(${classNameVar}Service.get${subSimpleClassName}(id));
+	}
+
+#end
+#end
+}

+ 0 - 13
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/_column.vm

@@ -1,13 +0,0 @@
-## 提供给 baseVO、createVO、updateVO 生成字段
-    @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end)
-#if (!${column.nullable})## 判断 @NotEmpty 和 @NotNull 注解
-#if (${field.fieldType} == 'String')
-    @NotEmpty(message = "${column.columnComment}不能为空")
-#else
-    @NotNull(message = "${column.columnComment}不能为空")
-#end
-#end
-#if (${column.javaType} == "LocalDateTime")## 时间类型
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-#end
-    private ${column.javaType} ${column.javaField};

+ 0 - 39
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/baseVO.vm

@@ -1,39 +0,0 @@
-package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import java.util.*;
-#foreach ($column in $columns)
-#if (${column.javaType} == "BigDecimal")
-import java.math.BigDecimal;
-#end
-#if (${column.javaType} == "LocalDateTime")
-import java.time.LocalDateTime;
-#end
-#end
-import javax.validation.constraints.*;
-## 处理 Date 字段的引入
-#foreach ($column in $columns)
-#if (${column.createOperation} && ${column.updateOperation} && ${column.listOperationResult}
-    && ${column.javaType} == "LocalDateTime")## 时间类型
-import org.springframework.format.annotation.DateTimeFormat;
-
-import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-#break
-#end
-#end
-
-/**
- * ${table.classComment} Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
-@Data
-public class ${sceneEnum.prefixClass}${table.className}BaseVO {
-
-#foreach ($column in $columns)
-#if (${column.createOperation} && ${column.updateOperation} && ${column.listOperationResult})##通用操作
-    #parse("codegen/java/controller/vo/_column.vm")
-
-#end
-#end
-}

+ 0 - 30
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/createReqVO.vm

@@ -1,30 +0,0 @@
-package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
-
-import lombok.*;
-import java.util.*;
-import io.swagger.v3.oas.annotations.media.Schema;
-import javax.validation.constraints.*;
-## 处理 Date 字段的引入
-#foreach ($column in $columns)
-#if (${column.createOperation} && (!${column.updateOperation} || !${column.listOperationResult})
-    && ${column.javaType} == "LocalDateTime")## 时间类型
-import org.springframework.format.annotation.DateTimeFormat;
-import java.time.LocalDateTime;
-import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-#break
-#end
-#end
-
-@Schema(description = "${sceneEnum.name} - ${table.classComment}创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class ${sceneEnum.prefixClass}${table.className}CreateReqVO extends ${sceneEnum.prefixClass}${table.className}BaseVO {
-
-#foreach ($column in $columns)
-#if (${column.createOperation} && (!${column.updateOperation} || !${column.listOperationResult}))##不是通用字段
-    #parse("codegen/java/controller/vo/_column.vm")
-
-#end
-#end
-}

+ 0 - 45
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/excelVO.vm

@@ -1,45 +0,0 @@
-package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import java.util.*;
-#foreach ($column in $columns)
-#if (${column.javaType} == "BigDecimal")
-import java.math.BigDecimal;
-#end
-#if (${column.javaType} == "LocalDateTime")
-import java.time.LocalDateTime;
-#end
-#end
-
-import com.alibaba.excel.annotation.ExcelProperty;
-#foreach ($column in $columns)
-#if ("$!column.dictType" != "")## 有设置数据字典
-import ${DictFormatClassName};
-import ${DictConvertClassName};
-
-#break
-#end
-#end
-
-/**
- * ${table.classComment} Excel VO
- *
- * @author ${table.author}
- */
-@Data
-public class ${sceneEnum.prefixClass}${table.className}ExcelVO {
-
-#foreach ($column in $columns)
-    #if (${column.listOperationResult})##返回字段
-    #if ("$!column.dictType" != "")##处理枚举值
-    @ExcelProperty(value = "${column.columnComment}", converter = DictConvert.class)
-    @DictFormat("${column.dictType}") // TODO 代码优化:建议设置到对应的 XXXDictTypeConstants 枚举类中
-    #else
-    @ExcelProperty("${column.columnComment}")
-    #end
-    private ${column.javaType} ${column.javaField};
-
-    #end
-#end
-}

+ 0 - 39
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/exportReqVO.vm

@@ -1,39 +0,0 @@
-package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
-
-import lombok.*;
-import java.util.*;
-import io.swagger.v3.oas.annotations.media.Schema;
-import ${PageParamClassName};
-## 处理 Date 字段的引入
-#foreach ($column in $columns)
-#if (${column.listOperation} && ${column.javaType} == "LocalDateTime")## 时间类型
-import java.time.LocalDateTime;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-#break
-#end
-#end
-## 字段模板
-#macro(columnTpl $prefix $prefixStr)
-    @Schema(description = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
-    private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
-#end
-
-@Schema(description = "${sceneEnum.name} - ${table.classComment} Excel 导出 Request VO,参数和 ${table.className}PageReqVO 是一致的")
-@Data
-public class ${sceneEnum.prefixClass}${table.className}ExportReqVO {
-
-#foreach ($column in $columns)
-#if (${column.listOperation})##查询操作
-#if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候
-    @Schema(description = "${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private ${column.javaType}[] ${column.javaField};
-#else##情况二,非 Between 的时间
-    #columnTpl('', '')
-#end
-
-#end
-#end
-}

+ 45 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/listReqVO.vm

@@ -0,0 +1,45 @@
+package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import ${PageParamClassName};
+#foreach ($column in $columns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#break
+#end
+#end
+## 处理 LocalDateTime 字段的引入
+#foreach ($column in $columns)
+#if (${column.listOperation} && ${column.javaType} == "LocalDateTime")
+import java.time.LocalDateTime;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+#break
+#end
+#end
+## 字段模板
+#macro(columnTpl $prefix $prefixStr)
+    @Schema(description = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
+    private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
+#end
+
+@Schema(description = "${sceneEnum.name} - ${table.classComment}列表 Request VO")
+@Data
+public class ${sceneEnum.prefixClass}${table.className}ListReqVO {
+
+#foreach ($column in $columns)
+#if (${column.listOperation})##查询操作
+#if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候
+    @Schema(description = "${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private ${column.javaType}[] ${column.javaField};
+#else##情况二,非 Between 的时间
+    #columnTpl('', '')
+#end
+
+#end
+#end
+}

+ 9 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/pageReqVO.vm

@@ -4,9 +4,15 @@ import lombok.*;
 import java.util.*;
 import io.swagger.v3.oas.annotations.media.Schema;
 import ${PageParamClassName};
-## 处理 Date 字段的引入
 #foreach ($column in $columns)
-#if (${column.listOperation} && ${column.javaType} == "LocalDateTime")## 时间类型
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#break
+#end
+#end
+## 处理 LocalDateTime 字段的引入
+#foreach ($column in $columns)
+#if (${column.listOperationCondition} && ${column.javaType} == "LocalDateTime")
 import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDateTime;
 
@@ -38,4 +44,4 @@ public class ${sceneEnum.prefixClass}${table.className}PageReqVO extends PagePar
 
 #end
 #end
-}
+}

+ 35 - 6
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/respVO.vm

@@ -2,24 +2,53 @@ package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePac
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
+import java.util.*;
+## 处理 BigDecimal 字段的引入
+import java.util.*;
 #foreach ($column in $columns)
-#if (${column.javaType} == "LocalDateTime")
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#break
+#end
+#end
+## 处理 LocalDateTime 字段的引入
+#foreach ($column in $columns)
+#if (${column.listOperationResult} && ${column.javaType} == "LocalDateTime")
+import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDateTime;
 #break
 #end
 #end
+## 处理 Excel 导出
+import com.alibaba.excel.annotation.*;
+#foreach ($column in $columns)
+#if ("$!column.dictType" != "")## 有设置数据字典
+import ${DictFormatClassName};
+import ${DictConvertClassName};
+#break
+#end
+#end
 
 @Schema(description = "${sceneEnum.name} - ${table.classComment} Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class ${sceneEnum.prefixClass}${table.className}RespVO extends ${sceneEnum.prefixClass}${table.className}BaseVO {
+@ExcelIgnoreUnannotated
+public class ${sceneEnum.prefixClass}${table.className}RespVO {
 
+## 逐个处理字段
 #foreach ($column in $columns)
-#if (${column.listOperationResult} && (!${column.createOperation} || !${column.updateOperation}))##不是通用字段
+#if (${column.listOperationResult})
+## 1. 处理 Swagger 注解
     @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end)
+## 2. 处理 Excel 导出
+#if ("$!column.dictType" != "")##处理枚举值
+    @ExcelProperty(value = "${column.columnComment}", converter = DictConvert.class)
+    @DictFormat("${column.dictType}") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+#else
+    @ExcelProperty("${column.columnComment}")
+#end
+## 3. 处理字段定义
     private ${column.javaType} ${column.javaField};
 
 #end
 #end
-}
+}

+ 65 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm

@@ -0,0 +1,65 @@
+package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+## 处理 BigDecimal 字段的引入
+import java.util.*;
+#foreach ($column in $columns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#break
+#end
+#end
+## 处理 LocalDateTime 字段的引入
+#foreach ($column in $columns)
+#if ((${column.createOperation} || ${column.updateOperation}) && ${column.javaType} == "LocalDateTime")
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+#break
+#end
+#end
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+#end
+
+@Schema(description = "${sceneEnum.name} - ${table.classComment}新增/修改 Request VO")
+@Data
+public class ${sceneEnum.prefixClass}${table.className}SaveReqVO {
+
+## 逐个处理字段
+#foreach ($column in $columns)
+#if (${column.createOperation} || ${column.updateOperation})
+## 1. 处理 Swagger 注解
+    @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end)
+## 2. 处理 Validator 参数校验
+#if (!${column.nullable} && !${column.primaryKey})
+#if (${column.javaType} == 'String')
+    @NotEmpty(message = "${column.columnComment}不能为空")
+#else
+    @NotNull(message = "${column.columnComment}不能为空")
+#end
+#end
+## 3. 处理字段定义
+    private ${column.javaType} ${column.javaField};
+
+#end
+#end
+## 特殊:主子表专属逻辑(非 ERP 模式)
+#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+    #if ( $subTable.subJoinMany)
+    @Schema(description = "${subTable.classComment}列表")
+    private List<${subTable.className}DO> ${subClassNameVars.get($index)}s;
+
+    #else
+    @Schema(description = "${subTable.classComment}")
+    private ${subTable.className}DO ${subClassNameVars.get($index)};
+
+    #end
+#end
+#end
+}

+ 0 - 30
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/updateReqVO.vm

@@ -1,30 +0,0 @@
-package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import java.util.*;
-import javax.validation.constraints.*;
-## 处理 Date 字段的引入
-#foreach ($column in $columns)
-#if (${column.updateOperation} && (!${column.createOperation} || !${column.listOperationResult})
-    && ${column.javaType} == "LocalDateTime")## 时间类型
-import org.springframework.format.annotation.DateTimeFormat;
-import java.time.LocalDateTime;
-import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-#break
-#end
-#end
-
-@Schema(description = "${sceneEnum.name} - ${table.classComment}更新 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class ${sceneEnum.prefixClass}${table.className}UpdateReqVO extends ${sceneEnum.prefixClass}${table.className}BaseVO {
-
-#foreach ($column in $columns)
-#if (${column.updateOperation} && (!${column.createOperation} || !${column.listOperationResult}))##不是通用字段
-    #parse("codegen/java/controller/vo/_column.vm")
-
-#end
-#end
-}

+ 0 - 34
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/convert/convert.vm

@@ -1,34 +0,0 @@
-package ${basePackage}.module.${table.moduleName}.convert.${table.businessName};
-
-import java.util.*;
-
-import ${PageResultClassName};
-
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
-import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
-
-/**
- * ${table.classComment} Convert
- *
- * @author ${table.author}
- */
-@Mapper
-public interface ${table.className}Convert {
-
-    ${table.className}Convert INSTANCE = Mappers.getMapper(${table.className}Convert.class);
-
-    ${table.className}DO convert(${sceneEnum.prefixClass}${table.className}CreateReqVO bean);
-
-    ${table.className}DO convert(${sceneEnum.prefixClass}${table.className}UpdateReqVO bean);
-
-    ${sceneEnum.prefixClass}${table.className}RespVO convert(${table.className}DO bean);
-
-    List<${sceneEnum.prefixClass}${table.className}RespVO> convertList(List<${table.className}DO> list);
-
-    PageResult<${sceneEnum.prefixClass}${table.className}RespVO> convertPage(PageResult<${table.className}DO> page);
-
-    List<${sceneEnum.prefixClass}${table.className}ExcelVO> convertList02(List<${table.className}DO> list);
-
-}

+ 6 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/do.vm

@@ -28,6 +28,11 @@ import ${BaseDOClassName};
 @AllArgsConstructor
 public class ${table.className}DO extends BaseDO {
 
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+    public static final Long ${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT = 0L;
+
+#end
 #foreach ($column in $columns)
 #if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段
     /**
@@ -44,4 +49,4 @@ public class ${table.className}DO extends BaseDO {
 #end
 #end
 
-}
+}

+ 49 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/do_sub.vm

@@ -0,0 +1,49 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+package ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName};
+
+import lombok.*;
+import java.util.*;
+#foreach ($column in $subColumns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#end
+#if (${column.javaType} == "LocalDateTime")
+import java.time.LocalDateTime;
+#end
+#end
+import com.baomidou.mybatisplus.annotation.*;
+import ${BaseDOClassName};
+
+/**
+ * ${subTable.classComment} DO
+ *
+ * @author ${subTable.author}
+ */
+@TableName("${subTable.tableName.toLowerCase()}")
+@KeySequence("${subTable.tableName.toLowerCase()}_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ${subTable.className}DO extends BaseDO {
+
+#foreach ($column in $subColumns)
+#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段
+    /**
+     * ${column.columnComment}
+    #if ("$!column.dictType" != "")##处理枚举值
+     *
+     * 枚举 {@link TODO ${column.dictType} 对应的类}
+    #end
+     */
+    #if (${column.primaryKey})##处理主键
+    @TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end
+    #end
+    private ${column.javaType} ${column.javaField};
+#end
+#end
+
+}

+ 19 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper.vm

@@ -49,18 +49,34 @@ import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePack
 @Mapper
 public interface ${table.className}Mapper extends BaseMapperX<${table.className}DO> {
 
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
     default PageResult<${table.className}DO> selectPage(${sceneEnum.prefixClass}${table.className}PageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<${table.className}DO>()
 			#listCondition()
                 .orderByDesc(${table.className}DO::getId));## 大多数情况下,id 倒序
 
     }
-
-    default List<${table.className}DO> selectList(${sceneEnum.prefixClass}${table.className}ExportReqVO reqVO) {
+#else
+    default List<${table.className}DO> selectList(${sceneEnum.prefixClass}${table.className}ListReqVO reqVO) {
         return selectList(new LambdaQueryWrapperX<${table.className}DO>()
 			#listCondition()
                 .orderByDesc(${table.className}DO::getId));## 大多数情况下,id 倒序
 
     }
+#end
+
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写
+#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写
+	default ${table.className}DO selectBy${TreeParentJavaField}And${TreeNameJavaField}(Long ${treeParentColumn.javaField}, String ${treeNameColumn.javaField}) {
+	    return selectOne(${table.className}DO::get${TreeParentJavaField}, ${treeParentColumn.javaField}, ${table.className}DO::get${TreeNameJavaField}, ${treeNameColumn.javaField});
+	}
+
+    default Long selectCountBy${TreeParentJavaField}(${treeParentColumn.javaType} ${treeParentColumn.javaField}) {
+        return selectCount(${table.className}DO::get${TreeParentJavaField}, ${treeParentColumn.javaField});
+    }
 
-}
+#end
+}

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper.xml.vm

@@ -9,4 +9,4 @@
         文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
      -->
 
-</mapper>
+</mapper>

+ 51 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper_sub.vm

@@ -0,0 +1,51 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subJoinColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+package ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName};
+
+import java.util.*;
+
+import ${PageResultClassName};
+import ${PageParamClassName};
+import ${QueryWrapperClassName};
+import ${BaseMapperClassName};
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * ${subTable.classComment} Mapper
+ *
+ * @author ${subTable.author}
+ */
+@Mapper
+public interface ${subTable.className}Mapper extends BaseMapperX<${subTable.className}DO> {
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+    default PageResult<${subTable.className}DO> selectPage(PageParam reqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<${subTable.className}DO>()
+            .eq(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField})
+            .orderByDesc(${subTable.className}DO::getId));## 大多数情况下,id 倒序
+
+    }
+
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+    #if ( $subTable.subJoinMany)
+    default List<${subTable.className}DO> selectListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return selectList(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
+    }
+
+    #else
+    default ${subTable.className}DO selectBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return selectOne(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
+    }
+
+    #end
+    #end
+    default int deleteBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+        return delete(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
+    }
+
+}

+ 19 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/enums/errorcode.vm

@@ -1,3 +1,22 @@
 // TODO 待办:请将下面的错误码复制到 yudao-module-${table.moduleName}-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
 // ========== ${table.classComment} TODO 补充编号 ==========
 ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${table.classComment}不存在");
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN = new ErrorCode(TODO 补充编号, "存在存在子${table.classComment},无法删除");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_NOT_EXITS = new ErrorCode(TODO 补充编号,"父级${table.classComment}不存在");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_ERROR = new ErrorCode(TODO 补充编号, "不能设置自己为父${table.classComment}");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE = new ErrorCode(TODO 补充编号, "已经存在该${treeNameColumn.columnComment}的${table.classComment}");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_IS_CHILD = new ErrorCode(TODO 补充编号, "不能设置自己的子${table.className}为父${table.className}");
+#end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 )## 特殊:ERP 情况
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($simpleClassNameUnderlineCase = $simpleClassNameUnderlineCases.get($index))
+ErrorCode ${simpleClassNameUnderlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${subTable.classComment}不存在");
+#if ( !$subTable.subJoinMany )
+ErrorCode ${simpleClassNameUnderlineCase.toUpperCase()}_EXISTS = new ErrorCode(TODO 补充编号, "${subTable.classComment}已存在");
+#end
+#end
+#end

+ 89 - 12
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/service.vm

@@ -4,7 +4,12 @@ import java.util.*;
 import javax.validation.*;
 import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
 import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+#end
 import ${PageResultClassName};
+import ${PageParamClassName};
 
 /**
  * ${table.classComment} Service 接口
@@ -19,14 +24,14 @@ public interface ${table.className}Service {
      * @param createReqVO 创建信息
      * @return 编号
      */
-    ${primaryColumn.javaType} create${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}CreateReqVO createReqVO);
+    ${primaryColumn.javaType} create${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO);
 
     /**
      * 更新${table.classComment}
      *
      * @param updateReqVO 更新信息
      */
-    void update${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}UpdateReqVO updateReqVO);
+    void update${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO);
 
     /**
      * 删除${table.classComment}
@@ -43,28 +48,100 @@ public interface ${table.className}Service {
      */
     ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id);
 
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
+    /**
+     * 获得${table.classComment}分页
+     *
+     * @param pageReqVO 分页查询
+     * @return ${table.classComment}分页
+     */
+    PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO);
+#else
     /**
      * 获得${table.classComment}列表
      *
-     * @param ids 编号
+     * @param listReqVO 查询条件
      * @return ${table.classComment}列表
      */
-    List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids);
+    List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO);
+#end
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subClassNameVar = $subClassNameVars.get($index))
+    // ==================== 子表($subTable.classComment) ====================
 
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
     /**
-     * 获得${table.classComment}分页
+     * 获得${subTable.classComment}分页
      *
      * @param pageReqVO 分页查询
-     * @return ${table.classComment}分页
+     * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}
+     * @return ${subTable.classComment}分页
      */
-    PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO);
+    PageResult<${subTable.className}DO> get${subSimpleClassName}Page(PageParam pageReqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField});
 
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+    #if ( $subTable.subJoinMany )
     /**
-     * 获得${table.classComment}列表, 用于 Excel 导出
+     * 获得${subTable.classComment}列表
      *
-     * @param exportReqVO 查询条件
-     * @return ${table.classComment}列表
+     * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}
+     * @return ${subTable.classComment}列表
+     */
+    List<${subTable.className}DO> get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField});
+
+    #else
+    /**
+     * 获得${subTable.classComment}
+     *
+     * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}
+     * @return ${subTable.classComment}
      */
-    List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO);
+    ${subTable.className}DO get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField});
+
+    #end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+    /**
+     * 创建${subTable.classComment}
+     *
+     * @param ${subClassNameVar} 创建信息
+     * @return 编号
+     */
+    ${subPrimaryColumn.javaType} create${subSimpleClassName}(@Valid ${subTable.className}DO ${subClassNameVar});
+
+    /**
+     * 更新${subTable.classComment}
+     *
+     * @param ${subClassNameVar} 更新信息
+     */
+    void update${subSimpleClassName}(@Valid ${subTable.className}DO ${subClassNameVar});
+
+    /**
+     * 删除${subTable.classComment}
+     *
+     * @param id 编号
+     */
+    void delete${subSimpleClassName}(${subPrimaryColumn.javaType} id);
+
+	/**
+	 * 获得${subTable.classComment}
+	 *
+	 * @param id 编号
+     * @return ${subTable.classComment}
+	 */
+    ${subTable.className}DO get${subSimpleClassName}(${subPrimaryColumn.javaType} id);
 
-}
+#end
+#end
+}

Some files were not shown because too many files changed in this diff