Browse Source

初始化提交

luoguangyi 3 years ago
commit
19aae6aa70
100 changed files with 5765 additions and 0 deletions
  1. 32 0
      .gitignore
  2. 50 0
      README.md
  3. 122 0
      iot-admin-server/pom.xml
  4. 15 0
      iot-admin-server/src/main/java/com/persagy/AdminServerApplication.java
  5. 9 0
      iot-admin-server/src/main/java/com/persagy/framework/async/config/AsyncConfiguration.java
  6. 9 0
      iot-admin-server/src/main/java/com/persagy/framework/monitor/config/AdminServerConfiguration.java
  7. 9 0
      iot-admin-server/src/main/java/com/persagy/modules/infra/codegen/config/CodegenConfiguration.java
  8. 28 0
      iot-admin-server/src/main/java/com/persagy/modules/infra/codegen/config/CodegenProperties.java
  9. 153 0
      iot-admin-server/src/main/java/com/persagy/modules/infra/controller/doc/InfDbDocController.java
  10. 33 0
      iot-admin-server/src/main/java/com/persagy/modules/infra/enums/InfErrorCodeConstants.java
  11. 103 0
      iot-admin-server/src/main/java/com/persagy/modules/infra/enums/SysErrorCodeConstants.java
  12. 155 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/ToolCodegenController.java
  13. 21 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/ToolCodegenDetailRespVO.java
  14. 17 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/ToolCodegenPreviewRespVO.java
  15. 49 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/ToolCodegenUpdateReqVO.java
  16. 85 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/column/ToolCodegenColumnBaseVO.java
  17. 23 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/column/ToolCodegenColumnRespVO.java
  18. 57 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/table/ToolCodegenTableBaseVO.java
  19. 35 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/table/ToolCodegenTablePageReqVO.java
  20. 26 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/table/ToolCodegenTableRespVO.java
  21. 25 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/table/ToolSchemaTableRespVO.java
  22. 5 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/ToolTestDemoController.http
  23. 96 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/ToolTestDemoController.java
  24. 34 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoBaseVO.java
  25. 14 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoCreateReqVO.java
  26. 37 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoExcelVO.java
  27. 39 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoExportReqVO.java
  28. 44 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoPageReqVO.java
  29. 23 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoRespVO.java
  30. 21 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoUpdateReqVO.java
  31. 70 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/convert/codegen/ToolCodegenConvert.java
  32. 36 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/convert/test/ToolTestDemoConvert.java
  33. 122 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/codegen/ToolCodegenColumnDO.java
  34. 93 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/codegen/ToolCodegenTableDO.java
  35. 54 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/codegen/ToolSchemaColumnDO.java
  36. 36 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/codegen/ToolSchemaTableDO.java
  37. 48 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/test/ToolTestDemoDO.java
  38. 22 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/codegen/ToolCodegenColumnMapper.java
  39. 25 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/codegen/ToolCodegenTableMapper.java
  40. 19 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/codegen/ToolSchemaColumnMapper.java
  41. 26 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/codegen/ToolSchemaTableMapper.java
  42. 43 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/test/ToolTestDemoMapper.java
  43. 12 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/enums/ToolDictTypeConstants.java
  44. 25 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/enums/ToolErrorCodeConstants.java
  45. 29 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/enums/codegen/ToolCodegenColumnHtmlTypeEnum.java
  46. 27 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/enums/codegen/ToolCodegenColumnListConditionEnum.java
  47. 23 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/enums/codegen/ToolCodegenImportTypeEnum.java
  48. 24 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/enums/codegen/ToolCodegenTemplateTypeEnum.java
  49. 36 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/config/ErrorCodeConfiguration.java
  50. 26 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/config/ErrorCodeProperties.java
  51. 34 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/dto/ErrorCodeAutoGenerateReqDTO.java
  52. 28 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/dto/ErrorCodeRespDTO.java
  53. 15 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/generator/ErrorCodeAutoGenerator.java
  54. 98 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/generator/ErrorCodeAutoGeneratorImpl.java
  55. 24 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/loader/ErrorCodeLoader.java
  56. 73 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java
  57. 35 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/service/ErrorCodeFrameworkService.java
  58. 121 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/ToolCodegenService.java
  59. 231 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/impl/ToolCodegenBuilder.java
  60. 194 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/impl/ToolCodegenEngine.java
  61. 108 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/impl/ToolCodegenSQLParser.java
  62. 284 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/impl/ToolCodegenServiceImpl.java
  63. 75 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/service/test/ToolTestDemoService.java
  64. 86 0
      iot-admin-server/src/main/java/com/persagy/modules/tool/service/test/impl/ToolTestDemoServiceImpl.java
  65. 131 0
      iot-admin-server/src/main/resources/application-local.yaml
  66. 62 0
      iot-admin-server/src/main/resources/application.yaml
  67. 15 0
      iot-admin-server/src/main/resources/banner.txt
  68. 92 0
      iot-admin-server/src/main/resources/codegen/java/controller/controller.vm
  69. 13 0
      iot-admin-server/src/main/resources/codegen/java/controller/vo/_column.vm
  70. 31 0
      iot-admin-server/src/main/resources/codegen/java/controller/vo/baseVO.vm
  71. 30 0
      iot-admin-server/src/main/resources/codegen/java/controller/vo/createReqVO.vm
  72. 37 0
      iot-admin-server/src/main/resources/codegen/java/controller/vo/excelVO.vm
  73. 42 0
      iot-admin-server/src/main/resources/codegen/java/controller/vo/exportReqVO.vm
  74. 44 0
      iot-admin-server/src/main/resources/codegen/java/controller/vo/pageReqVO.vm
  75. 20 0
      iot-admin-server/src/main/resources/codegen/java/controller/vo/respVO.vm
  76. 30 0
      iot-admin-server/src/main/resources/codegen/java/controller/vo/updateReqVO.vm
  77. 34 0
      iot-admin-server/src/main/resources/codegen/java/convert/convert.vm
  78. 38 0
      iot-admin-server/src/main/resources/codegen/java/dal/do.vm
  79. 66 0
      iot-admin-server/src/main/resources/codegen/java/dal/mapper.vm
  80. 2 0
      iot-admin-server/src/main/resources/codegen/java/enums/errorcode.vm
  81. 70 0
      iot-admin-server/src/main/resources/codegen/java/service/service.vm
  82. 83 0
      iot-admin-server/src/main/resources/codegen/java/service/serviceImpl.vm
  83. 161 0
      iot-admin-server/src/main/resources/codegen/java/test/serviceTest.vm
  84. 27 0
      iot-admin-server/src/main/resources/codegen/sql/sql.vm
  85. 55 0
      iot-admin-server/src/main/resources/codegen/vue/api/api.js.vm
  86. 403 0
      iot-admin-server/src/main/resources/codegen/vue/views/index.vue.vm
  87. 16 0
      iot-admin-server/src/main/resources/rebel.xml
  88. 139 0
      iot-common/pom.xml
  89. 15 0
      iot-common/src/main/java/com.persagy/framework/common/core/IntArrayValuable.java
  90. 20 0
      iot-common/src/main/java/com.persagy/framework/common/core/KeyValue.java
  91. 32 0
      iot-common/src/main/java/com.persagy/framework/common/exception/ErrorCode.java
  92. 60 0
      iot-common/src/main/java/com.persagy/framework/common/exception/ServiceException.java
  93. 44 0
      iot-common/src/main/java/com.persagy/framework/common/exception/enums/GlobalErrorCodeConstants.java
  94. 34 0
      iot-common/src/main/java/com.persagy/framework/common/exception/enums/ServiceErrorCodeRange.java
  95. 122 0
      iot-common/src/main/java/com.persagy/framework/common/exception/util/ServiceExceptionUtil.java
  96. 102 0
      iot-common/src/main/java/com.persagy/framework/common/pojo/CommonResult.java
  97. 30 0
      iot-common/src/main/java/com.persagy/framework/common/pojo/PageParam.java
  98. 38 0
      iot-common/src/main/java/com.persagy/framework/common/pojo/PageResult.java
  99. 56 0
      iot-common/src/main/java/com.persagy/framework/common/pojo/SortingField.java
  100. 0 0
      iot-common/src/main/java/com.persagy/framework/common/test/WebFilterOrderEnum.java

+ 32 - 0
.gitignore

@@ -0,0 +1,32 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/
+demo/

+ 50 - 0
README.md

@@ -0,0 +1,50 @@
+# 工程简介
+
+## Jackson
+### Jackson简介
+这个工程基于常见的MVC架构,展示了Jackson(the Java JSON library)的使用。学习这个工程之前,需要同学们对MVC有一定的了解。
+
+### Jackson工程解读
+
+本项目的工程结构如下。
+
+视图的不同可以通过“手动写逻辑”的方式来实现,但是在Spring Boot中,我们仅仅需要定义视图类、数据标注、API标注就更简便地达到相同的效果。
+
+```
+.
+├── README.md
+├── only3.json
+├── pom.xml
+└── src
+    └── main
+        ├── java
+        │   └── demo
+        │       └── jacksondemo
+        │           ├── Application.java//SpringBoot入口,main函数
+        │           ├── DO//数据对象。数据对象的视图在每个对象中定义。
+        │           │   ├── Message.java//数据结构标注
+        │           │   └── User.java//数据结构标注
+        │           ├── controller//Controller,接收前端的映射
+        │           │   └── MessageController.java//API标注
+        │           ├── service//接口与实现分离的服务
+        │           │   ├── MessageService.java
+        │           │   └── impl
+        │           │       └── MessageServiceImpl.java
+        │           └── view//视图类的定义
+        │               └── View.java//视图类
+        └── resources
+            └── application.properties//各种配置选项
+
+11 directories, 15 files
+```
+
+
+# 延伸阅读
+
+### Jackson延伸阅读
+
+* Jackson Homepage: https://github.com/FasterXML/jackson
+
+* Jackson教程:https://www.baeldung.com/category/json/jackson/
+
+* Java工程脚手架: https://start.aliyun.com

+ 122 - 0
iot-admin-server/pom.xml

@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.persagy</groupId>
+        <artifactId>iot</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>iot-admin-server</artifactId>
+    <packaging>jar</packaging>
+
+    <name>iot-admin-server</name>
+    <description>管理后台 Server,提供其 API 接口</description>
+
+    <dependencies>
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>com.persagy</groupId>
+            <artifactId>iot-spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- spring boot 配置所需依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- DB 相关 -->
+        <dependency>
+            <groupId>com.persagy</groupId>
+            <artifactId>iot-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+<!--        <dependency>-->
+<!--            <groupId>com.persagy</groupId>-->
+<!--            <artifactId>iot-spring-boot-starter-redis</artifactId>-->
+<!--        </dependency>-->
+
+        <!-- 监控相关 -->
+<!--        <dependency>-->
+<!--            <groupId>com.persagy</groupId>-->
+<!--            <artifactId>iot-spring-boot-starter-monitor</artifactId>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>de.codecentric</groupId>
+            <artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
+        </dependency>
+
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>com.persagy</groupId>
+            <artifactId>iot-spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.persagy</groupId>
+            <artifactId>iot-spring-boot-starter-excel</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.smallbun.screw</groupId>
+            <artifactId>screw-core</artifactId> <!-- 实现数据库文档 -->
+        </dependency>
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <!-- TODO 后续看情况,进行调整 -->
+        <dependency>
+            <groupId>com.xkcoding.justauth</groupId>
+            <artifactId>justauth-spring-boot-starter</artifactId>
+            <version>1.4.0</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <!-- 设置构建的 jar 包名 -->
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <!-- 打包 -->
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <fork>true</fork>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 15 - 0
iot-admin-server/src/main/java/com/persagy/AdminServerApplication.java

@@ -0,0 +1,15 @@
+package com.persagy;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+
+@SpringBootApplication
+@ServletComponentScan
+class AdminServerApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(AdminServerApplication.class, args);
+    }
+
+}

+ 9 - 0
iot-admin-server/src/main/java/com/persagy/framework/async/config/AsyncConfiguration.java

@@ -0,0 +1,9 @@
+package com.persagy.framework.async.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@Configuration
+@EnableAsync
+public class AsyncConfiguration {
+}

+ 9 - 0
iot-admin-server/src/main/java/com/persagy/framework/monitor/config/AdminServerConfiguration.java

@@ -0,0 +1,9 @@
+package com.persagy.framework.monitor.config;
+
+import de.codecentric.boot.admin.server.config.EnableAdminServer;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableAdminServer
+public class AdminServerConfiguration {
+}

+ 9 - 0
iot-admin-server/src/main/java/com/persagy/modules/infra/codegen/config/CodegenConfiguration.java

@@ -0,0 +1,9 @@
+package com.persagy.modules.infra.codegen.config;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties(CodegenProperties.class)
+public class CodegenConfiguration {
+}

+ 28 - 0
iot-admin-server/src/main/java/com/persagy/modules/infra/codegen/config/CodegenProperties.java

@@ -0,0 +1,28 @@
+package com.persagy.modules.infra.codegen.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Collection;
+
+@ConfigurationProperties(prefix = "biz.codegen")
+@Validated
+@Data
+public class CodegenProperties {
+
+    /**
+     * 生成的 Java 代码的基础包
+     */
+    @NotNull(message = "Java 代码的基础包不能为空")
+    private String basePackage;
+
+    /**
+     * 数据库名数组
+     */
+    @NotEmpty(message = "数据库不能为空")
+    private Collection<String> dbSchemas;
+
+}

+ 153 - 0
iot-admin-server/src/main/java/com/persagy/modules/infra/controller/doc/InfDbDocController.java

@@ -0,0 +1,153 @@
+package com.persagy.modules.infra.controller.doc;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.smallbun.screw.core.Configuration;
+import cn.smallbun.screw.core.engine.EngineConfig;
+import cn.smallbun.screw.core.engine.EngineFileType;
+import cn.smallbun.screw.core.engine.EngineTemplateType;
+import cn.smallbun.screw.core.execute.DocumentationExecute;
+import cn.smallbun.screw.core.process.ProcessConfig;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
+import com.persagy.framework.common.util.servlet.ServletUtils;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+
+@Api(tags = "数据库文档")
+@RestController
+@RequestMapping("/infra/db-doc")
+public class InfDbDocController {
+
+    private static final String FILE_OUTPUT_DIR = System.getProperty("java.io.tmpdir") + File.separator
+            + "db-doc";
+    private static final String DOC_FILE_NAME = "数据库文档";
+    private static final String DOC_VERSION = "1.0.0";
+    private static final String DOC_DESCRIPTION = "文档描述";
+    @Resource
+    private DynamicDataSourceProperties dynamicDataSourceProperties;
+
+    /**
+     * 创建 screw 的引擎配置
+     */
+    private static EngineConfig buildEngineConfig(EngineFileType fileOutputType, String docFileName) {
+        return EngineConfig.builder()
+                .fileOutputDir(FILE_OUTPUT_DIR) // 生成文件路径
+                .openOutputDir(false) // 打开目录
+                .fileType(fileOutputType) // 文件类型
+                .produceType(EngineTemplateType.freemarker) // 文件类型
+                .fileName(docFileName) // 自定义文件名称
+                .build();
+    }
+
+    /**
+     * 创建 screw 的处理配置,一般可忽略
+     * 指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置
+     */
+    private static ProcessConfig buildProcessConfig() {
+        return ProcessConfig.builder()
+                .ignoreTablePrefix(Collections.singletonList("QRTZ_")) // 忽略表前缀
+                .build();
+    }
+
+    @GetMapping("/export-html")
+    @ApiOperation("导出 html 格式的数据文档")
+    @ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true", dataTypeClass = Boolean.class)
+    public void exportHtml(@RequestParam(defaultValue = "true") Boolean deleteFile,
+                           HttpServletResponse response) throws IOException {
+        doExportFile(EngineFileType.HTML, deleteFile, response);
+    }
+
+    @GetMapping("/export-word")
+    @ApiOperation("导出 word 格式的数据文档")
+    @ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true", dataTypeClass = Boolean.class)
+    public void exportWord(@RequestParam(defaultValue = "true") Boolean deleteFile,
+                           HttpServletResponse response) throws IOException {
+        doExportFile(EngineFileType.WORD, deleteFile, response);
+    }
+
+    @GetMapping("/export-markdown")
+    @ApiOperation("导出 markdown 格式的数据文档")
+    @ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true", dataTypeClass = Boolean.class)
+    public void exportMarkdown(@RequestParam(defaultValue = "true") Boolean deleteFile,
+                               HttpServletResponse response) throws IOException {
+        doExportFile(EngineFileType.MD, deleteFile, response);
+    }
+
+    private void doExportFile(EngineFileType fileOutputType, Boolean deleteFile,
+                              HttpServletResponse response) throws IOException {
+        String docFileName = DOC_FILE_NAME + "_" + IdUtil.fastSimpleUUID();
+        String filePath = doExportFile(fileOutputType, docFileName);
+        String downloadFileName = DOC_FILE_NAME + fileOutputType.getFileSuffix(); //下载后的文件名
+        try {
+            // 读取,返回
+            ServletUtils.writeAttachment(response, downloadFileName, FileUtil.readBytes(filePath));
+        } finally {
+            handleDeleteFile(deleteFile, filePath);
+        }
+    }
+
+    /**
+     * 输出文件,返回文件路径
+     *
+     * @param fileOutputType 文件类型
+     * @param fileName       文件名, 无需 ".docx" 等文件后缀
+     * @return 生成的文件所在路径
+     */
+    private String doExportFile(EngineFileType fileOutputType, String fileName) {
+        try (HikariDataSource dataSource = buildDataSource()) {
+            // 创建 screw 的配置
+            Configuration config = Configuration.builder()
+                    .version(DOC_VERSION)  // 版本
+                    .description(DOC_DESCRIPTION) // 描述
+                    .dataSource(dataSource) // 数据源
+                    .engineConfig(buildEngineConfig(fileOutputType, fileName)) // 引擎配置
+                    .produceConfig(buildProcessConfig()) // 处理配置
+                    .build();
+
+            // 执行 screw,生成数据库文档
+            new DocumentationExecute(config).execute();
+
+            return FILE_OUTPUT_DIR + File.separator + fileName + fileOutputType.getFileSuffix();
+        }
+    }
+
+    private void handleDeleteFile(Boolean deleteFile, String filePath) {
+        if (!deleteFile) {
+            return;
+        }
+        FileUtil.del(filePath);
+    }
+
+    /**
+     * 创建数据源
+     */
+    // TODO  :screw 暂时不支持 druid,尴尬
+    private HikariDataSource buildDataSource() {
+        // 获得 DataSource 数据源,目前只支持首个
+        String primary = dynamicDataSourceProperties.getPrimary();
+        DataSourceProperty dataSourceProperty = dynamicDataSourceProperties.getDatasource().get(primary);
+        // 创建 HikariConfig 配置类
+        HikariConfig hikariConfig = new HikariConfig();
+        hikariConfig.setJdbcUrl(dataSourceProperty.getUrl());
+        hikariConfig.setUsername(dataSourceProperty.getUsername());
+        hikariConfig.setPassword(dataSourceProperty.getPassword());
+        hikariConfig.addDataSourceProperty("useInformationSchema", "true"); // 设置可以获取 tables remarks 信息
+        // 创建数据源
+        return new HikariDataSource(hikariConfig);
+    }
+
+}

+ 33 - 0
iot-admin-server/src/main/java/com/persagy/modules/infra/enums/InfErrorCodeConstants.java

@@ -0,0 +1,33 @@
+package com.persagy.modules.infra.enums;
+
+import com.persagy.framework.common.exception.ErrorCode;
+
+/**
+ * Infra 错误码枚举类
+ * <p>
+ * infra 系统,使用 1-001-000-000 段
+ */
+public interface InfErrorCodeConstants {
+
+    // ========== 参数配置 1001000000 ==========
+    ErrorCode CONFIG_NOT_EXISTS = new ErrorCode(1001000001, "参数配置不存在");
+    ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复");
+    ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1001000003, "不能删除类型为系统内置的参数配置");
+    ErrorCode CONFIG_GET_VALUE_ERROR_IF_SENSITIVE = new ErrorCode(1001000004, "不允许获取敏感配置到前端");
+
+    // ========== 定时任务 1001001000 ==========
+    ErrorCode JOB_NOT_EXISTS = new ErrorCode(1001001000, "定时任务不存在");
+    ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1001001001, "定时任务的处理器已经存在");
+    ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1001001002, "只允许修改为开启或者关闭状态");
+    ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1001001003, "定时任务已经处于该状态,无需修改");
+    ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1001001004, "只有开启状态的任务,才可以修改");
+    ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1001001005, "CRON 表达式不正确");
+
+    // ========== API 错误日志 1001002000 ==========
+    ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1001002000, "API 错误日志不存在");
+    ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1001002001, "API 错误日志已处理");
+
+    // ========== 文件 1001003000 ==========
+    ErrorCode FILE_NOT_EXISTS = new ErrorCode(1001003000, "文件不存在");
+
+}

+ 103 - 0
iot-admin-server/src/main/java/com/persagy/modules/infra/enums/SysErrorCodeConstants.java

@@ -0,0 +1,103 @@
+package com.persagy.modules.infra.enums;
+
+import com.persagy.framework.common.exception.ErrorCode;
+
+/**
+ * System 错误码枚举类
+ * <p>
+ * system 系统,使用 1-002-000-000 段
+ */
+public interface SysErrorCodeConstants {
+
+    // ========== AUTH 模块 1002000000 ==========
+    ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
+    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
+    ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登录失败的兜底,位置原因
+    ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
+    ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
+    ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
+
+    // ========== TOKEN 模块 1002001000 ==========
+    ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期");
+    ErrorCode TOKEN_PARSE_FAIL = new ErrorCode(1002001001, "Token 解析失败");
+
+    // ========== 菜单模块 1002002000 ==========
+    ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1002002000, "已经存在该名字的菜单");
+    ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1002002001, "父菜单不存在");
+    ErrorCode MENU_PARENT_ERROR = new ErrorCode(1002002002, "不能设置自己为父菜单");
+    ErrorCode MENU_NOT_EXISTS = new ErrorCode(1002002003, "菜单不存在");
+    ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1002002004, "存在子菜单,无法删除");
+    ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1002002005, "父菜单的类型必须是目录或者菜单");
+
+    // ========== 角色模块 1002003000 ==========
+    ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1002003000, "角色不存在");
+    ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1002003001, "已经存在名为【{}】的角色");
+    ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1002003002, "已经存在编码为【{}】的角色");
+    ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1002003004, "不能操作类型为系统内置的角色");
+
+    // ========== 用户模块 1002004000 ==========
+    ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1002004000, "用户账号已经存在");
+    ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1002004001, "手机号已经存在");
+    ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1002004002, "邮箱已经存在");
+    ErrorCode USER_NOT_EXISTS = new ErrorCode(1002004003, "用户不存在");
+    ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002004004, "导入用户数据不能为空!");
+    ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1002004005, "用户密码校验失败");
+
+    // ========== 部门模块 1002005000 ==========
+    ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1002004001, "已经存在该名字的部门");
+    ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1002004002, "父级部门不存在");
+    ErrorCode DEPT_NOT_FOUND = new ErrorCode(1002004003, "当前部门不存在");
+    ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1002004004, "存在子部门,无法删除");
+    ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1002004005, "不能设置自己为父部门");
+    ErrorCode DEPT_EXISTS_USER = new ErrorCode(1002004006, "部门中存在员工,无法删除");
+    ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1002004007, "部门不处于开启状态,不允许选择");
+    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1002004008, "不能设置自己的子部门为父部门");
+
+    // ========== 岗位模块 1002005000 ==========
+    ErrorCode POST_NOT_FOUND = new ErrorCode(1002005001, "当前岗位不存在");
+    ErrorCode POST_NOT_ENABLE = new ErrorCode(1002005002, "岗位({}) 不处于开启状态,不允许选择");
+    ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1002005001, "已经存在该名字的岗位");
+    ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1002005001, "已经存在该标识的岗位");
+
+    // ========== 字典类型 1002006000 ==========
+    ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1002006001, "当前字典类型不存在");
+    ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1002006002, "字典类型不处于开启状态,不允许选择");
+    ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1002006003, "已经存在该名字的字典类型");
+    ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1002006004, "已经存在该类型的字典类型");
+    ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1002006004, "无法删除,该字典类型还有字典数据");
+
+    // ========== 字典数据 1002007000 ==========
+    ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1002007001, "当前字典数据不存在");
+    ErrorCode DICT_DATA_NOT_ENABLE = new ErrorCode(1002007002, "字典数据不处于开启状态,不允许选择");
+    ErrorCode DICT_DATA_VALUE_DUPLICATE = new ErrorCode(1002007003, "已经存在该值的字典数据");
+
+    // ========== 通知公告 1002008000 ==========
+    ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1002008001, "当前通知公告不存在");
+
+    // ========== 文件 1002009000 ==========
+    ErrorCode FILE_PATH_EXISTS = new ErrorCode(1002009001, "文件路径已经存在");
+    ErrorCode FILE_UPLOAD_FAILED = new ErrorCode(1002009002, "文件上传失败");
+    ErrorCode FILE_IS_EMPTY = new ErrorCode(1002009003, "文件为空");
+
+    // ========== 短信渠道 1002011000 ==========
+    ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1002011000, "短信渠道不存在");
+    ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1002011001, "短信渠道不处于开启状态,不允许选择");
+    ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1002011002, "无法删除,该短信渠道还有短信模板");
+
+    // ========== 短信模板 1002011000 ==========
+    ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1002011000, "短信模板不存在");
+    ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002011001, "已经存在编码为【{}】的短信模板");
+
+    // ========== 短信发送 1002012000 ==========
+    ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1002012000, "手机号不存在");
+    ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002012001, "模板参数({})缺失");
+
+    // ========== 错误码模块 1002013000 ==========
+    ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1002013000, "错误码不存在");
+    ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1002013001, "已经存在编码为【{}】的错误码");
+
+    // ========== 社交模块 1002014000 ==========
+    ErrorCode SOCIAL_AUTH_FAILURE = new ErrorCode(1002014000, "社交授权失败,原因是:{}");
+    ErrorCode SOCIAL_UNBIND_NOT_SELF = new ErrorCode(1002014001, "社交解绑失败,非当前用户绑定");
+
+}

+ 155 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/ToolCodegenController.java

@@ -0,0 +1,155 @@
+package com.persagy.modules.tool.controller.codegen;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ZipUtil;
+import com.persagy.framework.common.pojo.CommonResult;
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.framework.common.util.collection.CollectionUtils;
+import com.persagy.framework.common.util.servlet.ServletUtils;
+import com.persagy.modules.tool.controller.codegen.vo.ToolCodegenDetailRespVO;
+import com.persagy.modules.tool.controller.codegen.vo.ToolCodegenPreviewRespVO;
+import com.persagy.modules.tool.controller.codegen.vo.ToolCodegenUpdateReqVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolCodegenTablePageReqVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolCodegenTableRespVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolSchemaTableRespVO;
+import com.persagy.modules.tool.convert.codegen.ToolCodegenConvert;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenTableDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaTableDO;
+import com.persagy.modules.tool.service.codegen.ToolCodegenService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.persagy.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "代码生成器")
+@RestController
+@RequestMapping("/tool/codegen")
+@Validated
+public class ToolCodegenController {
+
+    @Resource
+    private ToolCodegenService codegenService;
+
+    @GetMapping("/db/table/list")
+    @ApiOperation(value = "获得数据库自带的表定义列表", notes = "会过滤掉已经导入 Codegen 的表")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "tableName", value = "表名,模糊匹配", required = true, example = "yudao", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "tableComment", value = "描述,模糊匹配", required = true, example = "BDTP", dataTypeClass = String.class)
+    })
+    public CommonResult<List<ToolSchemaTableRespVO>> getSchemaTableList(
+            @RequestParam(value = "tableName", required = false) String tableName,
+            @RequestParam(value = "tableComment", required = false) String tableComment) {
+        // 获得数据库自带的表定义列表
+        List<ToolSchemaTableDO> schemaTables = codegenService.getSchemaTableList(tableName, tableComment);
+        // 移除在 Codegen 中,已经存在的
+        Set<String> existsTables = CollectionUtils.convertSet(codegenService.getCodeGenTableList(), ToolCodegenTableDO::getTableName);
+        schemaTables.removeIf(table -> existsTables.contains(table.getTableName()));
+        return success(ToolCodegenConvert.INSTANCE.convertList04(schemaTables));
+    }
+
+    @GetMapping("/table/page")
+    @ApiOperation("获得表定义分页")
+    public CommonResult<PageResult<ToolCodegenTableRespVO>> getCodeGenTablePage(@Valid ToolCodegenTablePageReqVO pageReqVO) {
+        PageResult<ToolCodegenTableDO> pageResult = codegenService.getCodegenTablePage(pageReqVO);
+        return success(ToolCodegenConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/detail")
+    @ApiOperation("获得表和字段的明细")
+    @ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
+    public CommonResult<ToolCodegenDetailRespVO> getCodegenDetail(@RequestParam("tableId") Long tableId) {
+        ToolCodegenTableDO table = codegenService.getCodegenTablePage(tableId);
+        List<ToolCodegenColumnDO> columns = codegenService.getCodegenColumnListByTableId(tableId);
+        // 拼装返回
+        return success(ToolCodegenConvert.INSTANCE.convert(table, columns));
+    }
+
+    @ApiOperation("基于数据库的表结构,创建代码生成器的表和字段定义")
+    @ApiImplicitParam(name = "tableNames", value = "表名数组", required = true, example = "sys_user", dataTypeClass = List.class)
+    @PostMapping("/create-list-from-db")
+    public CommonResult<List<Long>> createCodegenListFromDB(@RequestParam("tableNames") List<String> tableNames) {
+        return success(codegenService.createCodegenListFromDB(tableNames));
+    }
+
+    @ApiOperation("基于 SQL 建表语句,创建代码生成器的表和字段定义")
+    @ApiImplicitParam(name = "sql", value = "SQL 建表语句", required = true, example = "sql", dataTypeClass = String.class)
+    @PostMapping("/create-list-from-sql")
+    public CommonResult<Long> createCodegenListFromSQL(@RequestParam("sql") String sql) {
+        return success(codegenService.createCodegenListFromSQL(sql));
+    }
+
+    @ApiOperation("更新数据库的表和字段定义")
+    @PutMapping("/update")
+    public CommonResult<Boolean> updateCodegen(@Valid @RequestBody ToolCodegenUpdateReqVO updateReqVO) {
+        codegenService.updateCodegen(updateReqVO);
+        return success(true);
+    }
+
+    @ApiOperation("基于数据库的表结构,同步数据库的表和字段定义")
+    @PutMapping("/sync-from-db")
+    @ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
+    public CommonResult<Boolean> syncCodegenFromDB(@RequestParam("tableId") Long tableId) {
+        codegenService.syncCodegenFromDB(tableId);
+        return success(true);
+    }
+
+    @ApiOperation("基于 SQL 建表语句,同步数据库的表和字段定义")
+    @PutMapping("/sync-from-sql")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class),
+            @ApiImplicitParam(name = "sql", value = "SQL 建表语句", required = true, example = "sql", dataTypeClass = String.class)
+    })
+    public CommonResult<Boolean> syncCodegenFromSQL(@RequestParam("tableId") Long tableId,
+                                                    @RequestParam("sql") String sql) {
+        codegenService.syncCodegenFromSQL(tableId, sql);
+        return success(true);
+    }
+
+    @ApiOperation("删除数据库的表和字段定义")
+    @DeleteMapping("/delete")
+    @ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
+    public CommonResult<Boolean> deleteCodegen(@RequestParam("tableId") Long tableId) {
+        codegenService.deleteCodegen(tableId);
+        return success(true);
+    }
+
+    @ApiOperation("预览生成代码")
+    @GetMapping("/preview")
+    @ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
+    public CommonResult<List<ToolCodegenPreviewRespVO>> previewCodegen(@RequestParam("tableId") Long tableId) {
+        Map<String, String> codes = codegenService.generationCodes(tableId);
+        return success(ToolCodegenConvert.INSTANCE.convert(codes));
+    }
+
+    @ApiOperation("下载生成代码")
+    @GetMapping(value = "/download",produces = {"application/octet-stream"})
+    @ApiImplicitParam(name = "tableId", value = "表编号", required = true, example = "1024", dataTypeClass = Long.class)
+    public void downloadCodegen(@RequestParam("tableId") Long tableId,
+                                HttpServletResponse response) throws IOException {
+        // 生成代码
+        Map<String, String> codes = codegenService.generationCodes(tableId);
+        // 构建 zip 包
+        String[] paths = codes.keySet().toArray(new String[0]);
+        ByteArrayInputStream[] ins = codes.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipUtil.zip(outputStream, paths, ins);
+        // 输出
+        ServletUtils.writeAttachment(response, "codegen.zip", outputStream.toByteArray());
+    }
+
+}

+ 21 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/ToolCodegenDetailRespVO.java

@@ -0,0 +1,21 @@
+package com.persagy.modules.tool.controller.codegen.vo;
+
+import com.persagy.modules.tool.controller.codegen.vo.column.ToolCodegenColumnRespVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolCodegenTableRespVO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@ApiModel("代码生成表和字段的明细 Response VO")
+@Data
+public class ToolCodegenDetailRespVO {
+
+    @ApiModelProperty("表定义")
+    private ToolCodegenTableRespVO table;
+
+    @ApiModelProperty("字段定义")
+    private List<ToolCodegenColumnRespVO> columns;
+
+}

+ 17 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/ToolCodegenPreviewRespVO.java

@@ -0,0 +1,17 @@
+package com.persagy.modules.tool.controller.codegen.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@ApiModel(value = "代码生成预览 Response VO", description = "注意,每个文件都是一个该对象")
+@Data
+public class ToolCodegenPreviewRespVO {
+
+    @ApiModelProperty(value = "文件路径", required = true, example = "java/cn/iocoder/yudao/adminserver/modules/system/controller/test/SysTestDemoController.java")
+    private String filePath;
+
+    @ApiModelProperty(value = "代码", required = true, example = "Hello World")
+    private String code;
+
+}

+ 49 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/ToolCodegenUpdateReqVO.java

@@ -0,0 +1,49 @@
+package com.persagy.modules.tool.controller.codegen.vo;
+
+import com.persagy.modules.tool.controller.codegen.vo.column.ToolCodegenColumnBaseVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolCodegenTableBaseVO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@ApiModel("代码生成表和字段的修改 Request VO")
+@Data
+public class ToolCodegenUpdateReqVO {
+
+    @Valid // 校验内嵌的字段
+    @NotNull(message = "表定义不能为空")
+    private Table table;
+
+    @Valid // 校验内嵌的字段
+    @NotNull(message = "字段定义不能为空")
+    private List<Column> columns;
+
+    @ApiModel("更新表定义")
+    @Data
+    @EqualsAndHashCode(callSuper = true)
+    @ToString(callSuper = true)
+    public static class Table extends ToolCodegenTableBaseVO {
+
+        @ApiModelProperty(value = "编号", required = true, example = "1")
+        private Long id;
+
+    }
+
+    @ApiModel("更新表定义")
+    @Data
+    @EqualsAndHashCode(callSuper = true)
+    @ToString(callSuper = true)
+    public static class Column extends ToolCodegenColumnBaseVO {
+
+        @ApiModelProperty(value = "编号", required = true, example = "1")
+        private Long id;
+
+    }
+
+}

+ 85 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/column/ToolCodegenColumnBaseVO.java

@@ -0,0 +1,85 @@
+package com.persagy.modules.tool.controller.codegen.vo.column;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 代码生成字段定义 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class ToolCodegenColumnBaseVO {
+
+    @ApiModelProperty(value = "表编号", required = true, example = "1")
+    @NotNull(message = "表编号不能为空")
+    private Long tableId;
+
+    @ApiModelProperty(value = "字段名", required = true, example = "user_age")
+    @NotNull(message = "字段名不能为空")
+    private String columnName;
+
+    @ApiModelProperty(value = "字段类型", required = true, example = "int(11)")
+    @NotNull(message = "字段类型不能为空")
+    private String columnType;
+
+    @ApiModelProperty(value = "字段描述", required = true, example = "年龄")
+    @NotNull(message = "字段描述不能为空")
+    private String columnComment;
+
+    @ApiModelProperty(value = "是否允许为空", required = true, example = "true")
+    @NotNull(message = "是否允许为空不能为空")
+    private Boolean nullable;
+
+    @ApiModelProperty(value = "是否主键", required = true, example = "false")
+    @NotNull(message = "是否主键不能为空")
+    private Boolean primaryKey;
+
+    @ApiModelProperty(value = "是否自增", required = true, example = "true")
+    @NotNull(message = "是否自增不能为空")
+    private String autoIncrement;
+
+    @ApiModelProperty(value = "排序", required = true, example = "10")
+    @NotNull(message = "排序不能为空")
+    private Integer ordinalPosition;
+
+    @ApiModelProperty(value = "Java 属性类型", required = true, example = "userAge")
+    @NotNull(message = "Java 属性类型不能为空")
+    private String javaType;
+
+    @ApiModelProperty(value = "Java 属性名", required = true, example = "Integer")
+    @NotNull(message = "Java 属性名不能为空")
+    private String javaField;
+
+    @ApiModelProperty(value = "字典类型", example = "sys_gender")
+    private String dictType;
+
+    @ApiModelProperty(value = "数据示例", example = "1024")
+    private String example;
+
+    @ApiModelProperty(value = "是否为 Create 创建操作的字段", required = true, example = "true")
+    @NotNull(message = "是否为 Create 创建操作的字段不能为空")
+    private Boolean createOperation;
+
+    @ApiModelProperty(value = "是否为 Update 更新操作的字段", required = true, example = "false")
+    @NotNull(message = "是否为 Update 更新操作的字段不能为空")
+    private Boolean updateOperation;
+
+    @ApiModelProperty(value = "是否为 List 查询操作的字段", required = true, example = "true")
+    @NotNull(message = "是否为 List 查询操作的字段不能为空")
+    private Boolean listOperation;
+
+    @ApiModelProperty(value = "List 查询操作的条件类型", required = true, example = "LIKE", notes = "参见 ToolCodegenColumnListConditionEnum 枚举")
+    @NotNull(message = "List 查询操作的条件类型不能为空")
+    private String listOperationCondition;
+
+    @ApiModelProperty(value = "是否为 List 查询操作的返回字段", required = true, example = "true")
+    @NotNull(message = "是否为 List 查询操作的返回字段不能为空")
+    private Boolean listOperationResult;
+
+    @ApiModelProperty(value = "显示类型", required = true, example = "input")
+    @NotNull(message = "显示类型不能为空")
+    private String htmlType;
+
+}

+ 23 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/column/ToolCodegenColumnRespVO.java

@@ -0,0 +1,23 @@
+package com.persagy.modules.tool.controller.codegen.vo.column;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("代码生成字段定义 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ToolCodegenColumnRespVO extends ToolCodegenColumnBaseVO {
+
+    @ApiModelProperty(value = "编号", required = true, example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 57 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/table/ToolCodegenTableBaseVO.java

@@ -0,0 +1,57 @@
+package com.persagy.modules.tool.controller.codegen.vo.table;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 代码生成 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class ToolCodegenTableBaseVO {
+
+    @ApiModelProperty(value = "导入类型", required = true, example = "1", notes = "参见 ToolCodegenImportTypeEnum 枚举")
+    @NotNull(message = "导入类型不能为空")
+    private Integer importType;
+
+    @ApiModelProperty(value = "表名称", required = true, example = "yudao")
+    @NotNull(message = "表名称不能为空")
+    private String tableName;
+
+    @ApiModelProperty(value = "表描述", required = true, example = "BDTP")
+    @NotNull(message = "表描述不能为空")
+    private String tableComment;
+
+    @ApiModelProperty(value = "备注", example = "我是备注")
+    private String remark;
+
+    @ApiModelProperty(value = "模块名", required = true, example = "system")
+    @NotNull(message = "模块名不能为空")
+    private String moduleName;
+
+    @ApiModelProperty(value = "业务名", required = true, example = "codegen")
+    @NotNull(message = "业务名不能为空")
+    private String businessName;
+
+    @ApiModelProperty(value = "类名称", required = true, example = "ToolCodegenTable")
+    @NotNull(message = "类名称不能为空")
+    private String className;
+
+    @ApiModelProperty(value = "类描述", required = true, example = "代码生成器的表定义")
+    @NotNull(message = "类描述不能为空")
+    private String classComment;
+
+    @ApiModelProperty(value = "作者", required = true, example = "BDTP源码")
+    @NotNull(message = "作者不能为空")
+    private String author;
+
+    @ApiModelProperty(value = "模板类型", required = true, example = "1", notes = "参见 ToolCodegenTemplateTypeEnum 枚举")
+    @NotNull(message = "模板类型不能为空")
+    private Integer templateType;
+
+    @ApiModelProperty(value = "父菜单编号", example = "1024")
+    private Long parentMenuId;
+
+}

+ 35 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/table/ToolCodegenTablePageReqVO.java

@@ -0,0 +1,35 @@
+package com.persagy.modules.tool.controller.codegen.vo.table;
+
+import com.persagy.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static com.persagy.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("表定义分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ToolCodegenTablePageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "表名称", example = "yudao", notes = "模糊匹配")
+    private String tableName;
+
+    @ApiModelProperty(value = "表描述", example = "BDTP", notes = "模糊匹配")
+    private String tableComment;
+
+    @ApiModelProperty(value = "开始创建时间", example = "2020-10-24 00:00:00")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date beginCreateTime;
+
+    @ApiModelProperty(value = "结束创建时间", example = "2020-10-24 23:59:59")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date endCreateTime;
+
+}

+ 26 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/table/ToolCodegenTableRespVO.java

@@ -0,0 +1,26 @@
+package com.persagy.modules.tool.controller.codegen.vo.table;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("代码生成表定义 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ToolCodegenTableRespVO extends ToolCodegenTableBaseVO {
+
+    @ApiModelProperty(value = "编号", required = true, example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+    @ApiModelProperty(value = "更新时间", required = true)
+    private Date updateTime;
+
+}

+ 25 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/codegen/vo/table/ToolSchemaTableRespVO.java

@@ -0,0 +1,25 @@
+package com.persagy.modules.tool.controller.codegen.vo.table;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@ApiModel("数据字典的表定义 Response VO")
+@Data
+public class ToolSchemaTableRespVO {
+
+    @ApiModelProperty(value = "数据库", required = true, example = "yudao")
+    private String tableSchema;
+
+    @ApiModelProperty(value = "表名称", required = true, example = "yuanma")
+    private String tableName;
+
+    @ApiModelProperty(value = "表描述", required = true, example = "BDTP源码")
+    private String tableComment;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 5 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/ToolTestDemoController.http

@@ -0,0 +1,5 @@
+### 请求 /tool/test-demo/get 接口 => 成功
+GET {{baseUrl}}/tool/test-demo/get?id=1
+
+### 请求 /tool/test-demo/list 接口 => 成功
+GET {{baseUrl}}/tool/test-demo/list?ids=1

+ 96 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/ToolTestDemoController.java

@@ -0,0 +1,96 @@
+package com.persagy.modules.tool.controller.test;
+
+import cn.hutool.core.thread.ThreadUtil;
+import com.persagy.framework.common.pojo.CommonResult;
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.framework.excel.core.util.ExcelUtils;
+import com.persagy.modules.tool.controller.test.vo.*;
+import com.persagy.modules.tool.convert.test.ToolTestDemoConvert;
+import com.persagy.modules.tool.dal.dataobject.test.ToolTestDemoDO;
+import com.persagy.modules.tool.service.test.ToolTestDemoService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+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 java.util.concurrent.TimeUnit;
+
+import static com.persagy.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "测试示例")
+@RestController
+@RequestMapping("/tool/test-demo")
+@Validated
+public class ToolTestDemoController {
+
+    @Resource
+    private ToolTestDemoService testDemoService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建测试示例")
+    public CommonResult<Long> createTestDemo(@Valid @RequestBody ToolTestDemoCreateReqVO createReqVO) {
+        return success(testDemoService.createTestDemo(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新测试示例")
+    public CommonResult<Boolean> updateTestDemo(@Valid @RequestBody ToolTestDemoUpdateReqVO updateReqVO) {
+        testDemoService.updateTestDemo(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除测试示例")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    public CommonResult<Boolean> deleteTestDemo(@RequestParam("id") Long id) {
+        testDemoService.deleteTestDemo(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得测试示例")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+//    @Lock4j // 分布式锁
+    public CommonResult<ToolTestDemoRespVO> getTestDemo(@RequestParam("id") Long id) {
+        if (true) { // 测试分布式锁
+            ThreadUtil.sleep(5, TimeUnit.SECONDS);
+        }
+        ToolTestDemoDO testDemo = testDemoService.getTestDemo(id);
+        return success(ToolTestDemoConvert.INSTANCE.convert(testDemo));
+    }
+
+    @GetMapping("/list")
+    @ApiOperation("获得测试示例列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, dataTypeClass = List.class)
+//    @RateLimiter(name = "backendA")
+//    @BizTrace(id = "#ids", type = "'user'")
+    public CommonResult<List<ToolTestDemoRespVO>> getTestDemoList(@RequestParam("ids") Collection<Long> ids) {
+        List<ToolTestDemoDO> list = testDemoService.getTestDemoList(ids);
+        return success(ToolTestDemoConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得测试示例分页")
+    public CommonResult<PageResult<ToolTestDemoRespVO>> getTestDemoPage(@Valid ToolTestDemoPageReqVO pageVO) {
+        PageResult<ToolTestDemoDO> pageResult = testDemoService.getTestDemoPage(pageVO);
+        return success(ToolTestDemoConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出测试示例 Excel")
+    public void exportTestDemoExcel(@Valid ToolTestDemoExportReqVO exportReqVO,
+                                    HttpServletResponse response) throws IOException {
+        List<ToolTestDemoDO> list = testDemoService.getTestDemoList(exportReqVO);
+        // 导出 Excel
+        List<ToolTestDemoExcelVO> datas = ToolTestDemoConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "测试示例.xls", "数据", ToolTestDemoExcelVO.class, datas);
+    }
+
+}

+ 34 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoBaseVO.java

@@ -0,0 +1,34 @@
+package com.persagy.modules.tool.controller.test.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 字典类型 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class ToolTestDemoBaseVO {
+
+    @ApiModelProperty(value = "名字", required = true, example = "BDTP")
+    @NotNull(message = "名字不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "状态", required = true, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "类型", required = true, example = "2")
+    @NotNull(message = "类型不能为空")
+    private Integer type;
+
+    @ApiModelProperty(value = "分类", required = true, example = "3")
+    @NotNull(message = "分类不能为空")
+    private Integer category;
+
+    @ApiModelProperty(value = "备注", example = "我是备注")
+    private String remark;
+
+}

+ 14 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoCreateReqVO.java

@@ -0,0 +1,14 @@
+package com.persagy.modules.tool.controller.test.vo;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("字典类型创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ToolTestDemoCreateReqVO extends ToolTestDemoBaseVO {
+
+}

+ 37 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoExcelVO.java

@@ -0,0 +1,37 @@
+package com.persagy.modules.tool.controller.test.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 测试示例 Excel VO
+ *
+ * @author
+ */
+@Data
+public class ToolTestDemoExcelVO {
+
+    @ExcelProperty("编号")
+    private Long id;
+
+    @ExcelProperty("名字")
+    private String name;
+
+    @ExcelProperty(value = "状态")
+    private Integer status;
+
+    @ExcelProperty(value = "类型")
+    private Integer type;
+
+    @ExcelProperty(value = "分类")
+    private Integer category;
+
+    @ExcelProperty("备注")
+    private String remark;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+}

+ 39 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoExportReqVO.java

@@ -0,0 +1,39 @@
+package com.persagy.modules.tool.controller.test.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static com.persagy.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "字典类型 Excel 导出 Request VO", description = "参数和 ToolTestDemoPageReqVO 是一致的")
+@Data
+public class ToolTestDemoExportReqVO {
+
+    @ApiModelProperty(value = "名字", example = "BDTP")
+    private String name;
+
+    @ApiModelProperty(value = "状态", example = "1")
+    private Integer status;
+
+    @ApiModelProperty(value = "类型", example = "2")
+    private Integer type;
+
+    @ApiModelProperty(value = "分类", example = "3")
+    private Integer category;
+
+    @ApiModelProperty(value = "备注", example = "我是备注")
+    private String remark;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 44 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoPageReqVO.java

@@ -0,0 +1,44 @@
+package com.persagy.modules.tool.controller.test.vo;
+
+import com.persagy.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static com.persagy.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("字典类型分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ToolTestDemoPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "名字", example = "BDTP")
+    private String name;
+
+    @ApiModelProperty(value = "状态", example = "1")
+    private Integer status;
+
+    @ApiModelProperty(value = "类型", example = "2")
+    private Integer type;
+
+    @ApiModelProperty(value = "分类", example = "3")
+    private Integer category;
+
+    @ApiModelProperty(value = "备注", example = "我是备注")
+    private String remark;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 23 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoRespVO.java

@@ -0,0 +1,23 @@
+package com.persagy.modules.tool.controller.test.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("字典类型 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ToolTestDemoRespVO extends ToolTestDemoBaseVO {
+
+    @ApiModelProperty(value = "编号", required = true, example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 21 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/controller/test/vo/ToolTestDemoUpdateReqVO.java

@@ -0,0 +1,21 @@
+package com.persagy.modules.tool.controller.test.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("字典类型更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ToolTestDemoUpdateReqVO extends ToolTestDemoBaseVO {
+
+    @ApiModelProperty(value = "编号", required = true, example = "1")
+    @NotNull(message = "编号不能为空")
+    private Long id;
+
+}

+ 70 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/convert/codegen/ToolCodegenConvert.java

@@ -0,0 +1,70 @@
+package com.persagy.modules.tool.convert.codegen;
+
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.modules.tool.controller.codegen.vo.ToolCodegenDetailRespVO;
+import com.persagy.modules.tool.controller.codegen.vo.ToolCodegenPreviewRespVO;
+import com.persagy.modules.tool.controller.codegen.vo.ToolCodegenUpdateReqVO;
+import com.persagy.modules.tool.controller.codegen.vo.column.ToolCodegenColumnRespVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolCodegenTableRespVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolSchemaTableRespVO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenTableDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaTableDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Mapper
+public interface ToolCodegenConvert {
+
+    ToolCodegenConvert INSTANCE = Mappers.getMapper(ToolCodegenConvert.class);
+
+    // ========== ToolInformationSchemaTableDO 和 ToolInformationSchemaColumnDO 相关 ==========
+
+    ToolCodegenTableDO convert(ToolSchemaTableDO bean);
+
+    List<ToolCodegenColumnDO> convertList(List<ToolSchemaColumnDO> list);
+
+    ToolCodegenTableRespVO convert(ToolSchemaColumnDO bean);
+
+    // ========== ToolCodegenTableDO 相关 ==========
+
+//    List<ToolCodegenTableRespVO> convertList02(List<ToolCodegenTableDO> list);
+
+    ToolCodegenTableRespVO convert(ToolCodegenTableDO bean);
+
+    PageResult<ToolCodegenTableRespVO> convertPage(PageResult<ToolCodegenTableDO> page);
+
+    // ========== ToolCodegenTableDO 相关 ==========
+
+    List<ToolCodegenColumnRespVO> convertList02(List<ToolCodegenColumnDO> list);
+
+    ToolCodegenTableDO convert(ToolCodegenUpdateReqVO.Table bean);
+
+    List<ToolCodegenColumnDO> convertList03(List<ToolCodegenUpdateReqVO.Column> columns);
+
+    List<ToolSchemaTableRespVO> convertList04(List<ToolSchemaTableDO> list);
+
+    // ========== 其它 ==========
+
+    default ToolCodegenDetailRespVO convert(ToolCodegenTableDO table, List<ToolCodegenColumnDO> columns) {
+        ToolCodegenDetailRespVO respVO = new ToolCodegenDetailRespVO();
+        respVO.setTable(convert(table));
+        respVO.setColumns(convertList02(columns));
+        return respVO;
+    }
+
+    default List<ToolCodegenPreviewRespVO> convert(Map<String, String> codes) {
+        return codes.entrySet().stream().map(entry -> {
+            ToolCodegenPreviewRespVO respVO = new ToolCodegenPreviewRespVO();
+            respVO.setFilePath(entry.getKey());
+            respVO.setCode(entry.getValue());
+            return respVO;
+        }).collect(Collectors.toList());
+    }
+
+}

+ 36 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/convert/test/ToolTestDemoConvert.java

@@ -0,0 +1,36 @@
+package com.persagy.modules.tool.convert.test;
+
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoCreateReqVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoExcelVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoRespVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoUpdateReqVO;
+import com.persagy.modules.tool.dal.dataobject.test.ToolTestDemoDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 字典类型 Convert
+ *
+ * @author
+ */
+@Mapper
+public interface ToolTestDemoConvert {
+
+    ToolTestDemoConvert INSTANCE = Mappers.getMapper(ToolTestDemoConvert.class);
+
+    ToolTestDemoDO convert(ToolTestDemoCreateReqVO bean);
+
+    ToolTestDemoDO convert(ToolTestDemoUpdateReqVO bean);
+
+    ToolTestDemoRespVO convert(ToolTestDemoDO bean);
+
+    List<ToolTestDemoRespVO> convertList(List<ToolTestDemoDO> list);
+
+    PageResult<ToolTestDemoRespVO> convertPage(PageResult<ToolTestDemoDO> page);
+
+    List<ToolTestDemoExcelVO> convertList02(List<ToolTestDemoDO> list);
+
+}

+ 122 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/codegen/ToolCodegenColumnDO.java

@@ -0,0 +1,122 @@
+package com.persagy.modules.tool.dal.dataobject.codegen;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.persagy.framework.mybatis.core.dataobject.BaseDO;
+import com.persagy.modules.tool.enums.codegen.ToolCodegenColumnHtmlTypeEnum;
+import com.persagy.modules.tool.enums.codegen.ToolCodegenColumnListConditionEnum;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 代码生成 column 字段定义
+ *
+ * @author 
+ */
+@TableName(value = "tool_codegen_column", autoResultMap = true)
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = true)
+public class ToolCodegenColumnDO extends BaseDO {
+
+    /**
+     * ID 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 表编号
+     * <p>
+     * 关联 {@link ToolCodegenTableDO#getId()}
+     */
+    private Long tableId;
+
+    // ========== 表相关字段 ==========
+
+    /**
+     * 字段名
+     */
+    private String columnName;
+    /**
+     * 字段类型
+     */
+    private String columnType;
+    /**
+     * 字段描述
+     */
+    private String columnComment;
+    /**
+     * 是否允许为空
+     */
+    private Boolean nullable;
+    /**
+     * 是否主键
+     */
+    private Boolean primaryKey;
+    /**
+     * 是否自增
+     */
+    private Boolean autoIncrement;
+    /**
+     * 排序
+     */
+    private Integer ordinalPosition;
+
+    // ========== Java 相关字段 ==========
+
+    /**
+     * Java 属性类型
+     * <p>
+     * 例如说 String、Boolean 等等
+     */
+    private String javaType;
+    /**
+     * Java 属性名
+     */
+//    @NotBlank(message = "Java属性不能为空")
+    private String javaField;
+    /**
+     * 字典类型
+     */
+    private String dictType;
+    /**
+     * 数据示例,主要用于生成 Swagger 注解的 example 字段
+     */
+    private String example;
+
+    // ========== CRUD 相关字段 ==========
+
+    /**
+     * 是否为 Create 创建操作的字段
+     */
+    private Boolean createOperation;
+    /**
+     * 是否为 Update 更新操作的字段
+     */
+    private Boolean updateOperation;
+    /**
+     * 是否为 List 查询操作的字段
+     */
+    private Boolean listOperation;
+    /**
+     * List 查询操作的条件类型
+     * <p>
+     * 枚举 {@link ToolCodegenColumnListConditionEnum}
+     */
+    private String listOperationCondition;
+    /**
+     * 是否为 List 查询操作的返回字段
+     */
+    private Boolean listOperationResult;
+
+    // ========== UI 相关字段 ==========
+
+    /**
+     * 显示类型
+     * <p>
+     * 枚举 {@link ToolCodegenColumnHtmlTypeEnum}
+     */
+    private String htmlType;
+
+}

+ 93 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/codegen/ToolCodegenTableDO.java

@@ -0,0 +1,93 @@
+package com.persagy.modules.tool.dal.dataobject.codegen;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.persagy.framework.mybatis.core.dataobject.BaseDO;
+import com.persagy.modules.tool.enums.codegen.ToolCodegenTemplateTypeEnum;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 代码生成 table 表定义
+ *
+ * @author 
+ */
+@TableName(value = "tool_codegen_table", autoResultMap = true)
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = true)
+public class ToolCodegenTableDO extends BaseDO {
+
+    /**
+     * ID 编号
+     */
+    private Long id;
+
+    /**
+     * 导入类型
+     * <p>
+     * 枚举 {@link ToolCodegenTemplateTypeEnum}
+     */
+    private Integer importType;
+
+    // ========== 表相关字段 ==========
+
+    /**
+     * 表名称
+     */
+    private String tableName;
+    /**
+     * 表描述
+     */
+    private String tableComment;
+    /**
+     * 备注
+     */
+    private String remark;
+
+    // ========== 类相关字段 ==========
+
+    /**
+     * 模块名,即一级目录
+     * <p>
+     * 例如说,system、infra、tool 等等
+     */
+    private String moduleName;
+    /**
+     * 业务名,即二级目录
+     * <p>
+     * 例如说,user、permission、dict 等等
+     */
+    private String businessName;
+    /**
+     * 类名称(首字母大写)
+     * <p>
+     * 例如说,SysUser、SysMenu、SysDictData 等等
+     */
+    private String className;
+    /**
+     * 类描述
+     */
+    private String classComment;
+    /**
+     * 作者
+     */
+    private String author;
+
+    // ========== 生成相关字段 ==========
+
+    /**
+     * 模板类型
+     * <p>
+     * 枚举 {@link ToolCodegenTemplateTypeEnum}
+     */
+    private Integer templateType;
+
+    // ========== 菜单相关字段 ==========
+
+    /**
+     * 父菜单编号
+     */
+    private Long parentMenuId;
+
+}

+ 54 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/codegen/ToolSchemaColumnDO.java

@@ -0,0 +1,54 @@
+package com.persagy.modules.tool.dal.dataobject.codegen;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * MySQL 数据库中的 column 字段定义
+ *
+ * @author 
+ */
+@TableName(value = "information_schema.columns", autoResultMap = true)
+@Data
+@Builder
+public class ToolSchemaColumnDO {
+
+    /**
+     * 表名称
+     */
+    private String tableName;
+    /**
+     * 字段名
+     */
+    private String columnName;
+    /**
+     * 字段类型
+     */
+    private String columnType;
+    /**
+     * 字段描述
+     */
+    private String columnComment;
+    /**
+     * 是否允许为空
+     */
+    @TableField("case when is_nullable = 'yes' then '1' else '0' end")
+    private Boolean nullable;
+    /**
+     * 是否主键
+     */
+    @TableField("case when column_key = 'PRI' then '1' else '0' end")
+    private Boolean primaryKey;
+    /**
+     * 是否自增
+     */
+    @TableField("case when extra = 'auto_increment' then '1' else '0' end")
+    private Boolean autoIncrement;
+    /**
+     * 排序字段
+     */
+    private Integer ordinalPosition;
+
+}

+ 36 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/codegen/ToolSchemaTableDO.java

@@ -0,0 +1,36 @@
+package com.persagy.modules.tool.dal.dataobject.codegen;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * MySQL 数据库中的 table 表定义
+ *
+ * @author 
+ */
+@TableName(value = "information_schema.tables", autoResultMap = true)
+@Data
+@Builder
+public class ToolSchemaTableDO {
+
+    /**
+     * 数据库
+     */
+    private String tableSchema;
+    /**
+     * 表名称
+     */
+    private String tableName;
+    /**
+     * 表描述
+     */
+    private String tableComment;
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+}

+ 48 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/dataobject/test/ToolTestDemoDO.java

@@ -0,0 +1,48 @@
+package com.persagy.modules.tool.dal.dataobject.test;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.persagy.framework.mybatis.core.dataobject.BaseDO;
+import lombok.*;
+
+/**
+ * 字典类型 DO
+ *
+ * @author
+ */
+@TableName("tool_test_demo")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ToolTestDemoDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 名字
+     */
+    private String name;
+    /**
+     * 状态
+     */
+    private Integer status;
+    /**
+     * 类型
+     */
+    private Integer type;
+    /**
+     * 分类
+     */
+    private Integer category;
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 22 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/codegen/ToolCodegenColumnMapper.java

@@ -0,0 +1,22 @@
+package com.persagy.modules.tool.dal.mysql.codegen;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.persagy.framework.mybatis.core.mapper.BaseMapperX;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenColumnDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ToolCodegenColumnMapper extends BaseMapperX<ToolCodegenColumnDO> {
+
+    default List<ToolCodegenColumnDO> selectListByTableId(Long tableId) {
+        return selectList(new QueryWrapper<ToolCodegenColumnDO>().eq("table_id", tableId)
+                .orderByAsc("ordinal_position"));
+    }
+
+    default void deleteListByTableId(Long tableId) {
+        delete(new QueryWrapper<ToolCodegenColumnDO>().eq("table_id", tableId));
+    }
+
+}

+ 25 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/codegen/ToolCodegenTableMapper.java

@@ -0,0 +1,25 @@
+package com.persagy.modules.tool.dal.mysql.codegen;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.framework.mybatis.core.mapper.BaseMapperX;
+import com.persagy.framework.mybatis.core.query.QueryWrapperX;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolCodegenTablePageReqVO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenTableDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ToolCodegenTableMapper extends BaseMapperX<ToolCodegenTableDO> {
+
+    default ToolCodegenTableDO selectByTableName(String tableName) {
+        return selectOne(new QueryWrapper<ToolCodegenTableDO>().eq("table_name", tableName));
+    }
+
+    default PageResult<ToolCodegenTableDO> selectPage(ToolCodegenTablePageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new QueryWrapperX<ToolCodegenTableDO>()
+                .likeIfPresent("table_name", pageReqVO.getTableName())
+                .likeIfPresent("table_comment", pageReqVO.getTableComment())
+                .betweenIfPresent("create_time", pageReqVO.getBeginCreateTime(), pageReqVO.getEndCreateTime()));
+    }
+
+}

+ 19 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/codegen/ToolSchemaColumnMapper.java

@@ -0,0 +1,19 @@
+package com.persagy.modules.tool.dal.mysql.codegen;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.persagy.framework.mybatis.core.mapper.BaseMapperX;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaColumnDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ToolSchemaColumnMapper extends BaseMapperX<ToolSchemaColumnDO> {
+
+    default List<ToolSchemaColumnDO> selectListByTableName(String tableSchema, String tableName) {
+        return selectList(new QueryWrapper<ToolSchemaColumnDO>().eq("table_name", tableName)
+                .eq("table_schema", tableSchema)
+                .orderByAsc("ordinal_position"));
+    }
+
+}

+ 26 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/codegen/ToolSchemaTableMapper.java

@@ -0,0 +1,26 @@
+package com.persagy.modules.tool.dal.mysql.codegen;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.persagy.framework.mybatis.core.mapper.BaseMapperX;
+import com.persagy.framework.mybatis.core.query.QueryWrapperX;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaTableDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+@Mapper
+public interface ToolSchemaTableMapper extends BaseMapperX<ToolSchemaTableDO> {
+
+    default List<ToolSchemaTableDO> selectList(Collection<String> tableSchemas, String tableName, String tableComment) {
+        return selectList(new QueryWrapperX<ToolSchemaTableDO>().in("table_schema", tableSchemas)
+                .likeIfPresent("table_name", tableName)
+                .likeIfPresent("table_comment", tableComment));
+    }
+
+    default ToolSchemaTableDO selectByTableSchemaAndTableName(String tableSchema, String tableName) {
+        return selectOne(new QueryWrapper<ToolSchemaTableDO>().eq("table_schema", tableSchema)
+                .eq("table_name", tableName));
+    }
+
+}

+ 43 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/dal/mysql/test/ToolTestDemoMapper.java

@@ -0,0 +1,43 @@
+package com.persagy.modules.tool.dal.mysql.test;
+
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.framework.mybatis.core.mapper.BaseMapperX;
+import com.persagy.framework.mybatis.core.query.QueryWrapperX;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoExportReqVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoPageReqVO;
+import com.persagy.modules.tool.dal.dataobject.test.ToolTestDemoDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 字典类型 Mapper
+ *
+ * @author
+ */
+@Mapper
+public interface ToolTestDemoMapper extends BaseMapperX<ToolTestDemoDO> {
+
+    default PageResult<ToolTestDemoDO> selectPage(ToolTestDemoPageReqVO reqVO) {
+        return selectPage(reqVO, new QueryWrapperX<ToolTestDemoDO>()
+                .likeIfPresent("name", reqVO.getName())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("type", reqVO.getType())
+                .eqIfPresent("category", reqVO.getCategory())
+                .eqIfPresent("remark", reqVO.getRemark())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    default List<ToolTestDemoDO> selectList(ToolTestDemoExportReqVO reqVO) {
+        return selectList(new QueryWrapperX<ToolTestDemoDO>()
+                .likeIfPresent("name", reqVO.getName())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("type", reqVO.getType())
+                .eqIfPresent("category", reqVO.getCategory())
+                .eqIfPresent("remark", reqVO.getRemark())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+}

+ 12 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/enums/ToolDictTypeConstants.java

@@ -0,0 +1,12 @@
+package com.persagy.modules.tool.enums;
+
+/**
+ * Tool 字典类型的枚举类
+ *
+ * @author 
+ */
+public interface ToolDictTypeConstants {
+
+    String TEST_DEMO_TYPE = "tool_test_demo_type";
+
+}

+ 25 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/enums/ToolErrorCodeConstants.java

@@ -0,0 +1,25 @@
+package com.persagy.modules.tool.enums;
+
+import com.persagy.framework.common.exception.ErrorCode;
+
+/**
+ * Tool 错误码枚举类
+ * <p>
+ * tool 系统,使用 1-003-000-000 段
+ */
+public interface ToolErrorCodeConstants {
+
+    // ========== 字典类型(测试) 1003000000 ==========
+    ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1003000000, "测试示例不存在");
+
+    // ========== 代码生成器 1003001000 ==========
+    ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1003001000, "表定义已经存在");
+    ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1003001001, "导入的表不存在");
+    ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1003001002, "导入的字段不存在");
+    ErrorCode CODEGEN_PARSE_SQL_ERROR = new ErrorCode(1003001003, "解析 SQL 失败,请检查");
+    ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1003001004, "表定义不存在");
+    ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1003001005, "字段义不存在");
+    ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1003001006, "同步的字段不存在");
+    ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1003001007, "同步失败,不存在改变");
+
+}

+ 29 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/enums/codegen/ToolCodegenColumnHtmlTypeEnum.java

@@ -0,0 +1,29 @@
+package com.persagy.modules.tool.enums.codegen;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 代码生成器的字段 HTML 展示枚举
+ */
+@AllArgsConstructor
+@Getter
+public enum ToolCodegenColumnHtmlTypeEnum {
+
+    INPUT("input"), // 文本框
+    TEXTAREA("textarea"), // 文本域
+    SELECT("select"), // 下拉框
+    RADIO("radio"), // 单选框
+    CHECKBOX("checkbox"), // 复选框
+    DATETIME("datetime"), // 日期控件
+    UPLOAD_IMAGE("upload_image"), // 上传图片
+    UPLOAD_FILE("upload_file"), // 上传文件
+    EDITOR("editor"), // 富文本控件
+    ;
+
+    /**
+     * 条件
+     */
+    private final String type;
+
+}

+ 27 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/enums/codegen/ToolCodegenColumnListConditionEnum.java

@@ -0,0 +1,27 @@
+package com.persagy.modules.tool.enums.codegen;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 代码生成器的字段过滤条件枚举
+ */
+@AllArgsConstructor
+@Getter
+public enum ToolCodegenColumnListConditionEnum {
+
+    EQ("="),
+    NE("!="),
+    GT(">"),
+    GTE(">="),
+    LT("<"),
+    LTE("<="),
+    LIKE("LIKE"),
+    BETWEEN("BETWEEN");
+
+    /**
+     * 条件
+     */
+    private final String condition;
+
+}

+ 23 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/enums/codegen/ToolCodegenImportTypeEnum.java

@@ -0,0 +1,23 @@
+package com.persagy.modules.tool.enums.codegen;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 代码生成的导入类型
+ *
+ * @author 
+ */
+@AllArgsConstructor
+@Getter
+public enum ToolCodegenImportTypeEnum {
+
+    DB(1), // 从 information_schema 的 table 和 columns 表导入
+    SQL(2); // 基于建表 SQL 语句导入
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+
+}

+ 24 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/enums/codegen/ToolCodegenTemplateTypeEnum.java

@@ -0,0 +1,24 @@
+package com.persagy.modules.tool.enums.codegen;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 代码生成模板类型
+ *
+ * @author 
+ */
+@AllArgsConstructor
+@Getter
+public enum ToolCodegenTemplateTypeEnum {
+
+    CRUD(1), // 单表(增删改查)
+    TREE(2), // 树表(增删改查)
+    ;
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+
+}

+ 36 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/config/ErrorCodeConfiguration.java

@@ -0,0 +1,36 @@
+//package com.persagy.adminserver.modules.tool.framework.errorcode.config;
+//
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.generator.ErrorCodeAutoGenerator;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.loader.ErrorCodeLoader;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.service.ErrorCodeFrameworkService;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.loader.ErrorCodeLoaderImpl;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.generator.ErrorCodeAutoGeneratorImpl;
+//import org.springframework.beans.factory.annotation.Value;
+//import org.springframework.boot.context.properties.EnableConfigurationProperties;
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.scheduling.annotation.EnableScheduling;
+//
+///**
+// * 错误码配置类
+// */
+//@Configuration
+//@EnableConfigurationProperties(ErrorCodeProperties.class)
+//@EnableScheduling // 开启调度任务的功能,因为 ErrorCodeRemoteLoader 通过定时刷新错误码
+//public class ErrorCodeConfiguration {
+//
+//    @Bean
+//    public ErrorCodeAutoGenerator errorCodeAutoGenerator(@Value("${spring.application.name}") String applicationName,
+//                                                         ErrorCodeProperties errorCodeProperties,
+//                                                         ErrorCodeFrameworkService errorCodeFrameworkService) {
+//        return new ErrorCodeAutoGeneratorImpl(applicationName, errorCodeProperties.getConstantsClassList(),
+//                errorCodeFrameworkService);
+//    }
+//
+//    @Bean
+//    public ErrorCodeLoader errorCodeLoader(@Value("${spring.application.name}") String applicationName,
+//                                           ErrorCodeFrameworkService errorCodeFrameworkService) {
+//        return new ErrorCodeLoaderImpl(applicationName, errorCodeFrameworkService);
+//    }
+//
+//}

+ 26 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/config/ErrorCodeProperties.java

@@ -0,0 +1,26 @@
+package com.persagy.modules.tool.framework.errorcode.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 错误码的配置属性类
+ *
+ * @author dlyan
+ */
+@ConfigurationProperties("biz.error-code")
+@Data
+@Validated
+public class ErrorCodeProperties {
+
+    /**
+     * 错误码枚举类
+     */
+    @NotNull(message = "错误码枚举类不能为空")
+    private List<String> constantsClassList;
+
+}

+ 34 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/dto/ErrorCodeAutoGenerateReqDTO.java

@@ -0,0 +1,34 @@
+package com.persagy.modules.tool.framework.errorcode.core.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 错误码自动生成 DTO
+ *
+ * @author dylan
+ */
+@Data
+@Accessors(chain = true)
+public class ErrorCodeAutoGenerateReqDTO {
+
+    /**
+     * 应用名
+     */
+    @NotNull(message = "应用名不能为空")
+    private String applicationName;
+    /**
+     * 错误码编码
+     */
+    @NotNull(message = "错误码编码不能为空")
+    private Integer code;
+    /**
+     * 错误码错误提示
+     */
+    @NotEmpty(message = "错误码错误提示不能为空")
+    private String message;
+
+}

+ 28 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/dto/ErrorCodeRespDTO.java

@@ -0,0 +1,28 @@
+package com.persagy.modules.tool.framework.errorcode.core.dto;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 错误码的 Response DTO
+ *
+ * @author 
+ */
+@Data
+public class ErrorCodeRespDTO {
+
+    /**
+     * 错误码编码
+     */
+    private Integer code;
+    /**
+     * 错误码错误提示
+     */
+    private String message;
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+}

+ 15 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/generator/ErrorCodeAutoGenerator.java

@@ -0,0 +1,15 @@
+package com.persagy.modules.tool.framework.errorcode.core.generator;
+
+/**
+ * 错误码的自动生成器
+ *
+ * @author dylan
+ */
+public interface ErrorCodeAutoGenerator {
+
+    /**
+     * 将配置类到错误码写入数据库
+     */
+    void execute();
+
+}

+ 98 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/generator/ErrorCodeAutoGeneratorImpl.java

@@ -0,0 +1,98 @@
+//package com.persagy.adminserver.modules.tool.framework.errorcode.core.generator;
+//
+//import cn.hutool.core.collection.CollUtil;
+//import cn.hutool.core.util.ClassUtil;
+//import cn.hutool.core.util.ReflectUtil;
+//import com.persagy.framework.common.exception.ErrorCode;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.dto.ErrorCodeAutoGenerateReqDTO;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.service.ErrorCodeFrameworkService;
+//import lombok.RequiredArgsConstructor;
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.boot.context.event.ApplicationReadyEvent;
+//import org.springframework.context.event.EventListener;
+//import org.springframework.scheduling.annotation.Async;
+//
+//import java.util.ArrayList;
+//import java.util.Arrays;
+//import java.util.List;
+//
+///**
+// * ErrorCodeAutoGenerator 的实现类
+// * 目的是,扫描指定的 {@link #constantsClassList} 类,写入到 system 服务中
+// *
+// * @author dylan
+// */
+//@RequiredArgsConstructor
+//@Slf4j
+//public class ErrorCodeAutoGeneratorImpl implements ErrorCodeAutoGenerator {
+//
+//    /**
+//     * 应用分组
+//     */
+//    private final String applicationName;
+//    /**
+//     * 错误码枚举类
+//     */
+//    private final List<String> constantsClassList;
+//    /**
+//     * 错误码 Service
+//     */
+//    private final ErrorCodeFrameworkService errorCodeService;
+//
+//    @Override
+//    @EventListener(ApplicationReadyEvent.class)
+//    @Async // 异步,保证项目的启动过程,毕竟非关键流程
+//    public void execute() {
+//        // 第一步,解析错误码
+//        List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = parseErrorCode();
+//        log.info("[execute][解析到错误码数量为 ({}) 个]", autoGenerateDTOs.size());
+//
+//        // 第二步,写入到 system 服务
+//        errorCodeService.autoGenerateErrorCodes(autoGenerateDTOs);
+//        log.info("[execute][写入到 system 组件完成]");
+//    }
+//
+//    /**
+//     * 解析 constantsClassList 变量,转换成错误码数组
+//     *
+//     * @return 错误码数组
+//     */
+//    private List<ErrorCodeAutoGenerateReqDTO>  parseErrorCode() {
+//        // 校验 errorCodeConstantsClass 参数
+//        if (CollUtil.isEmpty(constantsClassList)) {
+//            log.info("[execute][未配置 yudao.error-code.constants-class-list 配置项,不进行自动写入到 system 服务中]");
+//            return new ArrayList<>();
+//        }
+//
+//        // 解析错误码
+//        List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = new ArrayList<>();
+//        constantsClassList.forEach(constantsClass -> {
+//            // 解析错误码枚举类
+//            Class<?> errorCodeConstantsClazz = ClassUtil.loadClass(constantsClass);
+//            // 解析错误码
+//            autoGenerateDTOs.addAll(parseErrorCode(errorCodeConstantsClazz));
+//        });
+//        return autoGenerateDTOs;
+//    }
+//
+//    /**
+//     * 解析错误码类,获得错误码数组
+//     *
+//     * @return 错误码数组
+//     */
+//    private List<ErrorCodeAutoGenerateReqDTO> parseErrorCode(Class<?> constantsClass) {
+//        List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = new ArrayList<>();
+//        Arrays.stream(constantsClass.getFields()).forEach(field -> {
+//            if (field.getType() != ErrorCode.class) {
+//                return;
+//            }
+//            // 转换成 ErrorCodeAutoGenerateReqDTO 对象
+//            ErrorCode errorCode = (ErrorCode) ReflectUtil.getFieldValue(constantsClass, field);
+//            autoGenerateDTOs.add(new ErrorCodeAutoGenerateReqDTO().setApplicationName(applicationName)
+//                    .setCode(errorCode.getCode()).setMessage(errorCode.getMsg()));
+//        });
+//        return autoGenerateDTOs;
+//    }
+//
+//}
+//

+ 24 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/loader/ErrorCodeLoader.java

@@ -0,0 +1,24 @@
+//package com.persagy.adminserver.modules.tool.framework.errorcode.core.loader;
+//
+//import com.persagy.framework.common.exception.util.ServiceExceptionUtil;
+//
+///**
+// * 错误码加载器
+// *
+// * 注意,错误码最终加载到 {@link ServiceExceptionUtil} 的 MESSAGES 变量中!
+// *
+// * @author dlyan
+// */
+//public interface ErrorCodeLoader {
+//
+//    /**
+//     * 添加错误码
+//     *
+//     * @param code 错误码的编号
+//     * @param msg 错误码的提示
+//     */
+//    default void putErrorCode(Integer code, String msg) {
+//        ServiceExceptionUtil.put(code, msg);
+//    }
+//
+//}

+ 73 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java

@@ -0,0 +1,73 @@
+//package com.persagy.adminserver.modules.tool.framework.errorcode.core.loader;
+//
+//import cn.hutool.core.collection.CollUtil;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.dto.ErrorCodeRespDTO;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.service.ErrorCodeFrameworkService;
+//import com.persagy.framework.common.util.date.DateUtils;
+//import lombok.RequiredArgsConstructor;
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.boot.context.event.ApplicationReadyEvent;
+//import org.springframework.context.event.EventListener;
+//import org.springframework.scheduling.annotation.Scheduled;
+//
+//import java.util.Date;
+//import java.util.List;
+//
+///**
+// * ErrorCodeLoader 的实现类,从 infra 的数据库中,加载错误码。
+// *
+// * 考虑到错误码会刷新,所以按照 {@link #REFRESH_ERROR_CODE_PERIOD} 频率,增量加载错误码。
+// *
+// * @author dlyan
+// */
+//@RequiredArgsConstructor
+//@Slf4j
+//public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
+//
+//    /**
+//     * 刷新错误码的频率,单位:毫秒
+//     */
+//    private static final int REFRESH_ERROR_CODE_PERIOD = 60 * 1000;
+//
+//    /**
+//     * 应用分组
+//     */
+//    private final String applicationName;
+//    /**
+//     * 错误码 Service
+//     */
+//    private final ErrorCodeFrameworkService errorCodeService;
+//
+//    /**
+//     * 缓存错误码的最大更新时间,用于后续的增量轮询,判断是否有更新
+//     */
+//    private Date maxUpdateTime;
+//
+//    @EventListener(ApplicationReadyEvent.class)
+//    public void loadErrorCodes() {
+//        this.loadErrorCodes0();
+//    }
+//
+//    @Scheduled(fixedDelay = REFRESH_ERROR_CODE_PERIOD, initialDelay = REFRESH_ERROR_CODE_PERIOD)
+//    public void refreshErrorCodes() {
+//        this.loadErrorCodes0();
+//    }
+//
+//    private void loadErrorCodes0() {
+//        // 加载错误码
+//        List<ErrorCodeRespDTO> errorCodeRespDTOs = errorCodeService.getErrorCodeList(applicationName, maxUpdateTime);
+//        if (CollUtil.isEmpty(errorCodeRespDTOs)) {
+//            return;
+//        }
+//        log.info("[loadErrorCodes0][加载到 ({}) 个错误码]", errorCodeRespDTOs.size());
+//
+//        // 刷新错误码的缓存
+//        errorCodeRespDTOs.forEach(errorCodeRespDTO -> {
+//            // 写入到错误码的缓存
+//            putErrorCode(errorCodeRespDTO.getCode(), errorCodeRespDTO.getMessage());
+//            // 记录下更新时间,方便增量更新
+//            maxUpdateTime = DateUtils.max(maxUpdateTime, errorCodeRespDTO.getUpdateTime());
+//        });
+//    }
+//
+//}

+ 35 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/framework/errorcode/core/service/ErrorCodeFrameworkService.java

@@ -0,0 +1,35 @@
+//package com.persagy.adminserver.modules.tool.framework.errorcode.core.service;
+//
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.dto.ErrorCodeAutoGenerateReqDTO;
+//import com.persagy.adminserver.modules.tool.framework.errorcode.core.dto.ErrorCodeRespDTO;
+//
+//import javax.validation.Valid;
+//import java.util.Date;
+//import java.util.List;
+//
+///**
+// * 错误码 Framework Service 接口
+// *
+// * @author 
+// */
+//public interface ErrorCodeFrameworkService {
+//
+//    /**
+//     * 自动创建错误码
+//     *
+//     * @param autoGenerateDTOs 错误码信息
+//     */
+//    void autoGenerateErrorCodes(@Valid List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs);
+//
+//    /**
+//     * 增量获得错误码数组
+//     *
+//     * 如果 minUpdateTime 为空时,则获取所有错误码
+//     *
+//     * @param applicationName 应用名
+//     * @param minUpdateTime 最小更新时间
+//     * @return 错误码数组
+//     */
+//    List<ErrorCodeRespDTO> getErrorCodeList(String applicationName, Date minUpdateTime);
+//
+//}

+ 121 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/ToolCodegenService.java

@@ -0,0 +1,121 @@
+package com.persagy.modules.tool.service.codegen;
+
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.modules.tool.controller.codegen.vo.ToolCodegenUpdateReqVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolCodegenTablePageReqVO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenTableDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaTableDO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 代码生成 Service 接口
+ *
+ * @author 
+ */
+public interface ToolCodegenService {
+
+    /**
+     * 基于 SQL 建表语句,创建代码生成器的表定义
+     *
+     * @param sql SQL 建表语句
+     * @return 创建的表定义的编号
+     */
+    Long createCodegenListFromSQL(String sql);
+
+    /**
+     * 基于数据库的表结构,创建代码生成器的表定义
+     *
+     * @param tableName 表名称
+     * @return 创建的表定义的编号
+     */
+    Long createCodegen(String tableName);
+
+    /**
+     * 基于 {@link #createCodegen(String)} 的批量创建
+     *
+     * @param tableNames 表名称数组
+     * @return 创建的表定义的编号数组
+     */
+    List<Long> createCodegenListFromDB(List<String> tableNames);
+
+    /**
+     * 更新数据库的表和字段定义
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateCodegen(ToolCodegenUpdateReqVO updateReqVO);
+
+    /**
+     * 基于数据库的表结构,同步数据库的表和字段定义
+     *
+     * @param tableId 表编号
+     */
+    void syncCodegenFromDB(Long tableId);
+
+    /**
+     * 基于 SQL 建表语句,同步数据库的表和字段定义
+     *
+     * @param tableId 表编号
+     * @param sql     SQL 建表语句
+     */
+    void syncCodegenFromSQL(Long tableId, String sql);
+
+    /**
+     * 删除数据库的表和字段定义
+     *
+     * @param tableId 数据编号
+     */
+    void deleteCodegen(Long tableId);
+
+    /**
+     * 获得表定义分页
+     *
+     * @param pageReqVO 分页条件
+     * @return 表定义分页
+     */
+    PageResult<ToolCodegenTableDO> getCodegenTablePage(ToolCodegenTablePageReqVO pageReqVO);
+
+    /**
+     * 获得表定义
+     *
+     * @param id 表编号
+     * @return 表定义
+     */
+    ToolCodegenTableDO getCodegenTablePage(Long id);
+
+    /**
+     * 获得全部表定义
+     *
+     * @return 表定义数组
+     */
+    List<ToolCodegenTableDO> getCodeGenTableList();
+
+    /**
+     * 获得指定表的字段定义数组
+     *
+     * @param tableId 表编号
+     * @return 字段定义数组
+     */
+    List<ToolCodegenColumnDO> getCodegenColumnListByTableId(Long tableId);
+
+    /**
+     * 执行指定表的代码生成
+     *
+     * @param tableId 表编号
+     * @return 生成结果。key 为文件路径,value 为对应的代码内容
+     */
+    Map<String, String> generationCodes(Long tableId);
+
+    /**
+     * 获得数据库自带的表定义列表
+     *
+     * @param tableName    表名称
+     * @param tableComment 表描述
+     * @return 表定义列表
+     */
+    List<ToolSchemaTableDO> getSchemaTableList(String tableName, String tableComment);
+
+}

+ 231 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/impl/ToolCodegenBuilder.java

@@ -0,0 +1,231 @@
+package com.persagy.modules.tool.service.codegen.impl;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import com.google.common.collect.Sets;
+import com.persagy.framework.mybatis.core.dataobject.BaseDO;
+import com.persagy.modules.tool.convert.codegen.ToolCodegenConvert;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenTableDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaTableDO;
+import com.persagy.modules.tool.enums.codegen.ToolCodegenColumnHtmlTypeEnum;
+import com.persagy.modules.tool.enums.codegen.ToolCodegenColumnListConditionEnum;
+import com.persagy.modules.tool.enums.codegen.ToolCodegenTemplateTypeEnum;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+import static cn.hutool.core.text.CharSequenceUtil.*;
+
+/**
+ * 代码生成器的 Builder,负责:
+ * 1. 将数据库的表 {@link ToolSchemaTableDO} 定义,构建成 {@link ToolCodegenTableDO}
+ * 2. 将数据库的列 {@link ToolSchemaColumnDO} 构定义,建成 {@link ToolCodegenColumnDO}
+ */
+@Component
+public class ToolCodegenBuilder {
+
+    /**
+     * {@link BaseDO} 的字段
+     */
+    public static final Set<String> BASE_DO_FIELDS = new HashSet<>();
+    /**
+     * Module 名字的映射 TODO 后续梳理到配置类
+     * <p>
+     * key:模块的完整名
+     * value:模块的缩写名
+     */
+    private static final Map<String, String> moduleNames = MapUtil.<String, String>builder()
+            .put("system", "sys")
+            .put("infra", "inf")
+            .put("tool", "tool")
+            .build();
+    /**
+     * 字段名与 {@link ToolCodegenColumnListConditionEnum} 的默认映射
+     * 注意,字段的匹配以后缀的方式
+     */
+    private static final Map<String, ToolCodegenColumnListConditionEnum> columnListOperationConditionMappings =
+            MapUtil.<String, ToolCodegenColumnListConditionEnum>builder()
+                    .put("name", ToolCodegenColumnListConditionEnum.LIKE)
+                    .put("time", ToolCodegenColumnListConditionEnum.BETWEEN)
+                    .put("date", ToolCodegenColumnListConditionEnum.BETWEEN)
+                    .build();
+    /**
+     * 字段名与 {@link ToolCodegenColumnHtmlTypeEnum} 的默认映射
+     * 注意,字段的匹配以后缀的方式
+     */
+    private static final Map<String, ToolCodegenColumnHtmlTypeEnum> columnHtmlTypeMappings =
+            MapUtil.<String, ToolCodegenColumnHtmlTypeEnum>builder()
+                    .put("status", ToolCodegenColumnHtmlTypeEnum.RADIO)
+                    .put("sex", ToolCodegenColumnHtmlTypeEnum.RADIO)
+                    .put("type", ToolCodegenColumnHtmlTypeEnum.SELECT)
+                    .put("image", ToolCodegenColumnHtmlTypeEnum.UPLOAD_IMAGE)
+                    .put("file", ToolCodegenColumnHtmlTypeEnum.UPLOAD_FILE)
+                    .put("content", ToolCodegenColumnHtmlTypeEnum.EDITOR)
+                    .put("time", ToolCodegenColumnHtmlTypeEnum.DATETIME)
+                    .put("date", ToolCodegenColumnHtmlTypeEnum.DATETIME)
+                    .build();
+    /**
+     * 新增操作,不需要传递的字段
+     */
+    private static final Set<String> CREATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id");
+    /**
+     * 修改操作,不需要传递的字段
+     */
+    private static final Set<String> UPDATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet();
+    /**
+     * 列表操作的条件,不需要传递的字段
+     */
+    private static final Set<String> LIST_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id");
+    /**
+     * 列表操作的结果,不需要返回的字段
+     */
+    private static final Set<String> LIST_OPERATION_RESULT_EXCLUDE_COLUMN = Sets.newHashSet();
+
+    /**
+     * Java 类型与 MySQL 类型的映射关系
+     */
+    private static final Map<String, Set<String>> javaTypeMappings = MapUtil.<String, Set<String>>builder()
+            .put(Boolean.class.getSimpleName(), Sets.newHashSet("bit"))
+            .put(Integer.class.getSimpleName(), Sets.newHashSet("tinyint", "smallint", "mediumint", "int"))
+            .put(Long.class.getSimpleName(), Collections.singleton("bigint"))
+            .put(Double.class.getSimpleName(), Sets.newHashSet("float", "double"))
+            .put(BigDecimal.class.getSimpleName(), Sets.newHashSet("decimal", "numeric"))
+            .put(String.class.getSimpleName(), Sets.newHashSet("tinytext", "text", "mediumtext", "longtext", // 长文本
+                    "char", "varchar", "nvarchar", "varchar2", "enum")) // 短文本
+            .put(Date.class.getSimpleName(), Sets.newHashSet("datetime", "time", "date", "timestamp"))
+            .put("byte[]", Sets.newHashSet("blob"))
+            .build();
+
+    static {
+        Arrays.stream(BaseDO.class.getDeclaredFields()).forEach(field -> BASE_DO_FIELDS.add(field.getName()));
+        // 处理 OPERATION 相关的字段
+        CREATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
+        UPDATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
+        LIST_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
+        LIST_OPERATION_EXCLUDE_COLUMN.remove("createTime"); // 创建时间,还是可能需要传递的
+        LIST_OPERATION_RESULT_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
+        LIST_OPERATION_RESULT_EXCLUDE_COLUMN.remove("createTime"); // 创建时间,还是需要返回的
+    }
+
+    public ToolCodegenTableDO buildTable(ToolSchemaTableDO schemaTable) {
+        ToolCodegenTableDO table = ToolCodegenConvert.INSTANCE.convert(schemaTable);
+        initTableDefault(table);
+        return table;
+    }
+
+    /**
+     * 初始化 Table 表的默认字段
+     *
+     * @param table 表定义
+     */
+    private void initTableDefault(ToolCodegenTableDO table) {
+        table.setModuleName(getFullModuleName(StrUtil.subBefore(table.getTableName(),
+                '_', false))); // 第一个 _ 前缀的前面,作为 module 名字
+        table.setBusinessName(toCamelCase(subAfter(table.getTableName(),
+                '_', false))); // 第一步,第一个 _ 前缀的后面,作为 module 名字; 第二步,可能存在多个 _ 的情况,转换成驼峰
+        table.setClassName(upperFirst(toCamelCase(table.getTableName()))); // 驼峰 + 首字母大写
+        table.setClassComment(subBefore(table.getTableComment(), // 去除结尾的表,作为类描述
+                '表', true));
+        table.setAuthor(" "); // TODO 稍后改成创建人
+        table.setTemplateType(ToolCodegenTemplateTypeEnum.CRUD.getType());
+    }
+
+    public List<ToolCodegenColumnDO> buildColumns(List<ToolSchemaColumnDO> schemaColumns) {
+        List<ToolCodegenColumnDO> columns = ToolCodegenConvert.INSTANCE.convertList(schemaColumns);
+        columns.forEach(this::initColumnDefault);
+        return columns;
+    }
+
+    /**
+     * 初始化 Column 列的默认字段
+     *
+     * @param column 列定义
+     */
+    private void initColumnDefault(ToolCodegenColumnDO column) {
+        // 处理 Java 相关的字段的默认值
+        processColumnJava(column);
+        // 处理 CRUD 相关的字段的默认值
+        processColumnOperation(column);
+        // 处理 UI 相关的字段的默认值
+        processColumnUI(column);
+    }
+
+    private void processColumnJava(ToolCodegenColumnDO column) {
+        // 处理 javaField 字段
+        column.setJavaField(toCamelCase(column.getColumnName()));
+        // 处理 dictType 字段,暂无
+        // 处理 javaType 字段
+        String dbType = StrUtil.subBefore(column.getColumnType(), '(', false);
+        javaTypeMappings.entrySet().stream()
+                .filter(entry -> entry.getValue().contains(dbType))
+                .findFirst().ifPresent(entry -> column.setJavaType(entry.getKey()));
+        if (column.getJavaType() == null) {
+            throw new IllegalStateException(String.format("column(%s) 的数据库类型(%s) 找不到匹配的 Java 类型",
+                    column.getColumnName(), column.getColumnType()));
+        }
+    }
+
+    private void processColumnOperation(ToolCodegenColumnDO column) {
+        // 处理 createOperation 字段
+        column.setCreateOperation(!CREATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
+                && !column.getPrimaryKey()); // 对于主键,创建时无需传递
+        // 处理 updateOperation 字段
+        column.setUpdateOperation(!UPDATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
+                || column.getPrimaryKey()); // 对于主键,更新时需要传递
+        // 处理 listOperation 字段
+        column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())
+                && !column.getPrimaryKey()); // 对于主键,列表过滤不需要传递
+        // 处理 listOperationCondition 字段
+        columnListOperationConditionMappings.entrySet().stream()
+                .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))
+                .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition()));
+        if (column.getListOperationCondition() == null) {
+            column.setListOperationCondition(ToolCodegenColumnListConditionEnum.EQ.getCondition());
+        }
+        // 处理 listOperationResult 字段
+        column.setListOperationResult(!LIST_OPERATION_RESULT_EXCLUDE_COLUMN.contains(column.getJavaField()));
+    }
+
+    private void processColumnUI(ToolCodegenColumnDO column) {
+        // 基于后缀进行匹配
+        columnHtmlTypeMappings.entrySet().stream()
+                .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(ToolCodegenColumnHtmlTypeEnum.RADIO.getType());
+        }
+        // 兜底,设置默认为 input 类型
+        if (column.getHtmlType() == null) {
+            column.setHtmlType(ToolCodegenColumnHtmlTypeEnum.INPUT.getType());
+        }
+    }
+
+    /**
+     * 获得模块的缩略名
+     *
+     * @param fullModuleName 模块的完整名
+     * @return 缩略名
+     */
+    public String getSimpleModuleName(String fullModuleName) {
+        return moduleNames.getOrDefault(fullModuleName, fullModuleName);
+    }
+
+    /**
+     * 获得模块的完整名
+     *
+     * @param shortModuleName 模块的缩略名
+     * @return 完整名
+     */
+    public String getFullModuleName(String shortModuleName) {
+        return moduleNames.entrySet().stream()
+                .filter(entry -> entry.getValue().equals(shortModuleName)) // 匹配
+                .findFirst().map(Map.Entry::getKey) // 返回 key
+                .orElse(shortModuleName); // 兜底返回 shortModuleName
+    }
+
+}

+ 194 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/impl/ToolCodegenEngine.java

@@ -0,0 +1,194 @@
+package com.persagy.modules.tool.service.codegen.impl;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.template.TemplateConfig;
+import cn.hutool.extra.template.TemplateEngine;
+import cn.hutool.extra.template.engine.velocity.VelocityEngine;
+import com.google.common.collect.Maps;
+import com.persagy.framework.common.exception.util.ServiceExceptionUtil;
+import com.persagy.framework.common.pojo.CommonResult;
+import com.persagy.framework.common.pojo.PageParam;
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.framework.common.util.collection.CollectionUtils;
+import com.persagy.framework.common.util.date.DateUtils;
+import com.persagy.framework.excel.core.util.ExcelUtils;
+import com.persagy.framework.mybatis.core.dataobject.BaseDO;
+import com.persagy.framework.mybatis.core.mapper.BaseMapperX;
+import com.persagy.framework.mybatis.core.query.QueryWrapperX;
+import com.persagy.modules.infra.codegen.config.CodegenProperties;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenTableDO;
+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 static cn.hutool.core.map.MapUtil.getStr;
+import static cn.hutool.core.text.CharSequenceUtil.*;
+
+/**
+ * 代码生成的引擎,用于具体生成代码
+ * 目前基于 {@link org.apache.velocity.app.Velocity} 模板引擎实现
+ * <p>
+ * 考虑到 Java 模板引擎的框架非常多,Freemarker、Velocity、Thymeleaf 等等,所以我们采用 hutool 封装的 {@link cn.hutool.extra.template.Template} 抽象
+ *
+ * @author 
+ */
+@Component
+public class ToolCodegenEngine {
+
+    /**
+     * 模板配置
+     * key:模板在 resources 的地址
+     * value:生成的路径
+     */
+    private static final Map<String, String> TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序
+            // Java Main
+            .put(javaTemplatePath("controller/vo/baseVO"),
+                    javaFilePath("controller/${table.businessName}/vo/${table.className}BaseVO"))
+            .put(javaTemplatePath("controller/vo/createReqVO"),
+                    javaFilePath("controller/${table.businessName}/vo/${table.className}CreateReqVO"))
+            .put(javaTemplatePath("controller/vo/pageReqVO"),
+                    javaFilePath("controller/${table.businessName}/vo/${table.className}PageReqVO"))
+            .put(javaTemplatePath("controller/vo/respVO"),
+                    javaFilePath("controller/${table.businessName}/vo/${table.className}RespVO"))
+            .put(javaTemplatePath("controller/vo/updateReqVO"),
+                    javaFilePath("controller/${table.businessName}/vo/${table.className}UpdateReqVO"))
+            .put(javaTemplatePath("controller/vo/exportReqVO"),
+                    javaFilePath("controller/${table.businessName}/vo/${table.className}ExportReqVO"))
+            .put(javaTemplatePath("controller/vo/excelVO"),
+                    javaFilePath("controller/${table.businessName}/vo/${table.className}ExcelVO"))
+            .put(javaTemplatePath("controller/controller"),
+                    javaFilePath("controller/${table.businessName}/${table.className}Controller"))
+            .put(javaTemplatePath("convert/convert"),
+                    javaFilePath("convert/${table.businessName}/${table.className}Convert"))
+            .put(javaTemplatePath("dal/do"),
+                    javaFilePath("dal/dataobject/${table.businessName}/${table.className}DO"))
+            .put(javaTemplatePath("dal/mapper"),
+                    javaFilePath("dal/mysql/${table.businessName}/${table.className}Mapper"))
+            .put(javaTemplatePath("enums/errorcode"),
+                    javaFilePath("enums/${simpleModuleName_upperFirst}ErrorCodeConstants"))
+            .put(javaTemplatePath("service/serviceImpl"),
+                    javaFilePath("service/${table.businessName}/impl/${table.className}ServiceImpl"))
+            .put(javaTemplatePath("service/service"),
+                    javaFilePath("service/${table.businessName}/${table.className}Service"))
+            // Java Test
+            .put(javaTemplatePath("test/serviceTest"),
+                    javaFilePath("service/${table.businessName}/${table.className}ServiceTest"))
+            // Vue
+            .put(vueTemplatePath("views/index.vue"),
+                    vueFilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
+            .put(vueTemplatePath("api/api.js"),
+                    vueFilePath("api/${table.moduleName}/${classNameVar}.js"))
+            // SQL
+            .put("codegen/sql/sql.vm", "sql/sql.sql")
+            .build();
+    /**
+     * 模板引擎,由 hutool 实现
+     */
+    private final TemplateEngine templateEngine;
+    /**
+     * 全局通用变量映射
+     */
+    private final Map<String, Object> globalBindingMap = new HashMap<>();
+    @Resource
+    private ToolCodegenBuilder codegenBuilder;
+    @Resource
+    private CodegenProperties codegenProperties;
+
+    public ToolCodegenEngine() {
+        // 初始化 TemplateEngine 属性
+        TemplateConfig config = new TemplateConfig();
+        config.setResourceMode(TemplateConfig.ResourceMode.CLASSPATH);
+        this.templateEngine = new VelocityEngine(config);
+    }
+
+    private static String javaTemplatePath(String path) {
+        return "codegen/java/" + path + ".vm";
+    }
+
+    private static String javaFilePath(String path) {
+        return "java/${basePackage}/${table.moduleName}/" + path + ".java";
+    }
+
+    private static String vueTemplatePath(String path) {
+        return "codegen/vue/" + path + ".vm";
+    }
+
+    private static String vueFilePath(String path) {
+        return "vue/" + path;
+    }
+
+    @PostConstruct
+    private void initGlobalBindingMap() {
+        // 全局配置
+        globalBindingMap.put("basePackage", codegenProperties.getBasePackage());
+        // 全局 Java Bean
+        globalBindingMap.put("CommonResultClassName", CommonResult.class.getName());
+        globalBindingMap.put("PageResultClassName", PageResult.class.getName());
+        // VO 类,独有字段
+        globalBindingMap.put("PageParamClassName", PageParam.class.getName());
+        // DO 类,独有字段
+        globalBindingMap.put("baseDOFields", ToolCodegenBuilder.BASE_DO_FIELDS);
+        globalBindingMap.put("BaseDOClassName", BaseDO.class.getName());
+        globalBindingMap.put("QueryWrapperClassName", QueryWrapperX.class.getName());
+        globalBindingMap.put("BaseMapperClassName", BaseMapperX.class.getName());
+        // Util 工具类
+        globalBindingMap.put("ServiceExceptionUtilClassName", ServiceExceptionUtil.class.getName());
+        globalBindingMap.put("DateUtilsClassName", DateUtils.class.getName());
+        globalBindingMap.put("ExcelUtilsClassName", ExcelUtils.class.getName());
+    }
+
+    public Map<String, String> execute(ToolCodegenTableDO table, List<ToolCodegenColumnDO> columns) {
+        // 创建 bindingMap
+        Map<String, Object> bindingMap = new HashMap<>(globalBindingMap);
+        bindingMap.put("table", table);
+        bindingMap.put("columns", columns);
+        bindingMap.put("primaryColumn", CollectionUtils.findFirst(columns, ToolCodegenColumnDO::getPrimaryKey)); // 主键字段
+        // moduleName 相关
+        String simpleModuleName = codegenBuilder.getSimpleModuleName(table.getModuleName());
+        bindingMap.put("simpleModuleName", simpleModuleName); // 将 system 转成 sys
+        bindingMap.put("simpleModuleName_upperFirst", upperFirst(simpleModuleName)); // 将 sys 转成 Sys
+        // className 相关
+        // 去掉指定前缀  将 TestDictType 转换成 DictType. 因为在 create 等方法后,不需要带上 Test 前缀
+        String simpleClassName = removePrefix(table.getClassName(), upperFirst(simpleModuleName));
+        bindingMap.put("simpleClassName", simpleClassName);
+        bindingMap.put("simpleClassName_underlineCase", toUnderlineCase(simpleClassName)); // 将 DictType 转换成 dict_type
+        bindingMap.put("classNameVar", lowerFirst(simpleClassName)); // 将 DictType 转换成 dictType,用于变量
+        String simpleClassNameStrikeCase = toSymbolCase(simpleClassName, '-'); // 将 DictType 转换成 dict-type
+        bindingMap.put("simpleClassName_strikeCase", simpleClassNameStrikeCase);
+        // permission 前缀
+        bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase);
+
+        // 执行生成
+        final Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(TEMPLATES.size()); // 有序
+        TEMPLATES.forEach((vmPath, filePath) -> {
+            filePath = formatFilePath(filePath, bindingMap);
+            String content = templateEngine.getTemplate(vmPath).render(bindingMap);
+            result.put(filePath, content);
+        });
+        return result;
+    }
+
+    private String formatFilePath(String filePath, Map<String, Object> bindingMap) {
+        filePath = StrUtil.replace(filePath, "${basePackage}",
+                getStr(bindingMap, "basePackage").replaceAll("\\.", "/"));
+        filePath = StrUtil.replace(filePath, "${simpleModuleName_upperFirst}",
+                getStr(bindingMap, "simpleModuleName_upperFirst"));
+        filePath = StrUtil.replace(filePath, "${classNameVar}",
+                getStr(bindingMap, "classNameVar"));
+
+        // table 包含的字段
+        ToolCodegenTableDO table = (ToolCodegenTableDO) bindingMap.get("table");
+        filePath = StrUtil.replace(filePath, "${table.moduleName}", table.getModuleName());
+        filePath = StrUtil.replace(filePath, "${table.businessName}", table.getBusinessName());
+        filePath = StrUtil.replace(filePath, "${table.className}", table.getClassName());
+        return filePath;
+    }
+
+}

+ 108 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/impl/ToolCodegenSQLParser.java

@@ -0,0 +1,108 @@
+package com.persagy.modules.tool.service.codegen.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.alibaba.druid.DbType;
+import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
+import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
+import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
+import com.alibaba.druid.sql.ast.statement.SQLPrimaryKey;
+import com.alibaba.druid.sql.ast.statement.SQLTableElement;
+import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
+import com.alibaba.druid.sql.repository.SchemaRepository;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaTableDO;
+import org.apache.commons.collections4.KeyValue;
+import org.apache.commons.collections4.keyvalue.DefaultKeyValue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.alibaba.druid.sql.SQLUtils.normalize;
+
+/**
+ * SQL 解析器,将创建表的 SQL,解析成 {@link ToolSchemaTableDO} 和 {@link ToolSchemaColumnDO} 对象,
+ * 后续可以基于它们,生成代码~
+ *
+ * @author 
+ */
+public class ToolCodegenSQLParser {
+
+    /**
+     * 解析建表 SQL 语句,返回 {@link ToolSchemaTableDO} 和 {@link ToolSchemaColumnDO} 对象
+     *
+     * @param sql 建表 SQL 语句
+     * @return 解析结果
+     */
+    public static KeyValue<ToolSchemaTableDO, List<ToolSchemaColumnDO>> parse(String sql) {
+        // 解析 SQL 成 Statement
+        SQLCreateTableStatement statement = parseCreateSQL(sql);
+        // 解析 Table 表
+        ToolSchemaTableDO table = parseTable(statement);
+        // 解析 Column 字段
+        List<ToolSchemaColumnDO> columns = parseColumns(statement);
+        columns.forEach(column -> column.setTableName(table.getTableName()));
+        // 返回
+        return new DefaultKeyValue<>(table, columns);
+    }
+
+    /**
+     * 使用 Druid 工具,建表 SQL 语句
+     *
+     * @param sql 建表 SQL 语句
+     * @return 创建 Statement
+     */
+    private static SQLCreateTableStatement parseCreateSQL(String sql) {
+        // 解析 SQL
+        SchemaRepository repository = new SchemaRepository(DbType.mysql);
+        repository.console(sql);
+        // 获得该表对应的 MySqlCreateTableStatement 对象
+        String tableName = CollUtil.getFirst(repository.getDefaultSchema().getObjects()).getName();
+        return (MySqlCreateTableStatement) repository.findTable(tableName).getStatement();
+    }
+
+    private static ToolSchemaTableDO parseTable(SQLCreateTableStatement statement) {
+        return ToolSchemaTableDO.builder()
+                .tableName(statement.getTableSource().getTableName(true))
+                .tableComment(((SQLCharExpr) statement.getComment()).getText())
+                .build();
+    }
+
+    private static List<ToolSchemaColumnDO> parseColumns(SQLCreateTableStatement statement) {
+        List<ToolSchemaColumnDO> columns = new ArrayList<>();
+        statement.getTableElementList().forEach(element -> parseColumn(columns, element));
+        return columns;
+    }
+
+    private static void parseColumn(List<ToolSchemaColumnDO> columns, SQLTableElement element) {
+        // 处理主键
+        if (element instanceof SQLPrimaryKey) {
+            parsePrimaryKey(columns, (SQLPrimaryKey) element);
+            return;
+        }
+        // 处理字段定义
+        if (element instanceof SQLColumnDefinition) {
+            parseColumnDefinition(columns, (SQLColumnDefinition) element);
+        }
+    }
+
+    private static void parsePrimaryKey(List<ToolSchemaColumnDO> columns, SQLPrimaryKey primaryKey) {
+        String columnName = normalize(primaryKey.getColumns().get(0).toString()); // 暂时不考虑联合主键
+        // 匹配 columns 主键字段,设置为 primary
+        columns.stream().filter(column -> column.getColumnName().equals(columnName))
+                .forEach(column -> column.setPrimaryKey(true));
+    }
+
+    private static void parseColumnDefinition(List<ToolSchemaColumnDO> columns, SQLColumnDefinition definition) {
+        String text = definition.toString().toUpperCase();
+        columns.add(ToolSchemaColumnDO.builder()
+                .columnName(normalize(definition.getColumnName()))
+                .columnType(definition.getDataType().toString())
+                .columnComment(normalize(definition.getComment().toString()))
+                .nullable(!text.contains(" NOT NULL"))
+                .primaryKey(false)
+                .autoIncrement(text.contains("AUTO_INCREMENT"))
+                .ordinalPosition(columns.size() + 1)
+                .build());
+    }
+
+}

+ 284 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/service/codegen/impl/ToolCodegenServiceImpl.java

@@ -0,0 +1,284 @@
+package com.persagy.modules.tool.service.codegen.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.framework.common.util.collection.CollectionUtils;
+import com.persagy.modules.infra.codegen.config.CodegenProperties;
+import com.persagy.modules.tool.controller.codegen.vo.ToolCodegenUpdateReqVO;
+import com.persagy.modules.tool.controller.codegen.vo.table.ToolCodegenTablePageReqVO;
+import com.persagy.modules.tool.convert.codegen.ToolCodegenConvert;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolCodegenTableDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaColumnDO;
+import com.persagy.modules.tool.dal.dataobject.codegen.ToolSchemaTableDO;
+import com.persagy.modules.tool.dal.mysql.codegen.ToolCodegenColumnMapper;
+import com.persagy.modules.tool.dal.mysql.codegen.ToolCodegenTableMapper;
+import com.persagy.modules.tool.dal.mysql.codegen.ToolSchemaColumnMapper;
+import com.persagy.modules.tool.dal.mysql.codegen.ToolSchemaTableMapper;
+import com.persagy.modules.tool.enums.codegen.ToolCodegenImportTypeEnum;
+import com.persagy.modules.tool.service.codegen.ToolCodegenService;
+import org.apache.commons.collections4.KeyValue;
+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.stream.Collectors;
+
+import static com.persagy.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.persagy.modules.tool.enums.ToolErrorCodeConstants.*;
+
+/**
+ * 代码生成 Service 实现类
+ *
+ * @author 
+ */
+@Service
+public class ToolCodegenServiceImpl implements ToolCodegenService {
+
+    @Resource
+    private ToolSchemaTableMapper schemaTableMapper;
+    @Resource
+    private ToolSchemaColumnMapper schemaColumnMapper;
+    @Resource
+    private ToolCodegenTableMapper codegenTableMapper;
+    @Resource
+    private ToolCodegenColumnMapper codegenColumnMapper;
+
+    @Resource
+    private ToolCodegenBuilder codegenBuilder;
+    @Resource
+    private ToolCodegenEngine codegenEngine;
+
+    @Resource
+    private CodegenProperties codegenProperties;
+
+    private Long createCodegen0(ToolCodegenImportTypeEnum importType,
+                                ToolSchemaTableDO schemaTable, List<ToolSchemaColumnDO> schemaColumns) {
+        // 校验导入的表和字段非空
+        if (schemaTable == null) {
+            throw exception(CODEGEN_IMPORT_TABLE_NULL);
+        }
+        if (CollUtil.isEmpty(schemaColumns)) {
+            throw exception(CODEGEN_IMPORT_COLUMNS_NULL);
+        }
+        // 校验是否已经存在
+        if (codegenTableMapper.selectByTableName(schemaTable.getTableName()) != null) {
+            throw exception(CODEGEN_TABLE_EXISTS);
+        }
+
+        // 构建 ToolCodegenTableDO 对象,插入到 DB 中
+        ToolCodegenTableDO table = codegenBuilder.buildTable(schemaTable);
+        table.setImportType(importType.getType());
+        codegenTableMapper.insert(table);
+        // 构建 ToolCodegenColumnDO 数组,插入到 DB 中
+        List<ToolCodegenColumnDO> columns = codegenBuilder.buildColumns(schemaColumns);
+        columns.forEach(column -> {
+            column.setTableId(table.getId());
+            codegenColumnMapper.insert(column); // TODO 批量插入
+        });
+        return table.getId();
+    }
+
+    @Override
+    public Long createCodegenListFromSQL(String sql) {
+        // 从 SQL 中,获得数据库表结构
+        ToolSchemaTableDO schemaTable;
+        List<ToolSchemaColumnDO> schemaColumns;
+        try {
+            KeyValue<ToolSchemaTableDO, List<ToolSchemaColumnDO>> result = ToolCodegenSQLParser.parse(sql);
+            schemaTable = result.getKey();
+            schemaColumns = result.getValue();
+        } catch (Exception ex) {
+            throw exception(CODEGEN_PARSE_SQL_ERROR);
+        }
+        // 导入
+        return this.createCodegen0(ToolCodegenImportTypeEnum.SQL, schemaTable, schemaColumns);
+    }
+
+    @Override
+    public Long createCodegen(String tableName) {
+        // 获取当前schema
+        String tableSchema = codegenProperties.getDbSchemas().iterator().next();
+        // 从数据库中,获得数据库表结构
+        ToolSchemaTableDO schemaTable = schemaTableMapper.selectByTableSchemaAndTableName(tableSchema, tableName);
+        List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableSchema, tableName);
+        // 导入
+        return this.createCodegen0(ToolCodegenImportTypeEnum.DB, schemaTable, schemaColumns);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public List<Long> createCodegenListFromDB(List<String> tableNames) {
+        List<Long> ids = new ArrayList<>(tableNames.size());
+        // 遍历添加。虽然效率会低一点,但是没必要做成完全批量,因为不会这么大量
+        tableNames.forEach(tableName -> ids.add(createCodegen(tableName)));
+        return ids;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateCodegen(ToolCodegenUpdateReqVO updateReqVO) {
+        // 校验是否已经存在
+        if (codegenTableMapper.selectById(updateReqVO.getTable().getId()) == null) {
+            throw exception(CODEGEN_TABLE_NOT_EXISTS);
+        }
+
+        // 更新 table 表定义
+        ToolCodegenTableDO updateTableObj = ToolCodegenConvert.INSTANCE.convert(updateReqVO.getTable());
+        codegenTableMapper.updateById(updateTableObj);
+        // 更新 column 字段定义
+        List<ToolCodegenColumnDO> updateColumnObjs = ToolCodegenConvert.INSTANCE.convertList03(updateReqVO.getColumns());
+        updateColumnObjs.forEach(updateColumnObj -> codegenColumnMapper.updateById(updateColumnObj));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void syncCodegenFromDB(Long tableId) {
+        // 校验是否已经存在
+        ToolCodegenTableDO table = codegenTableMapper.selectById(tableId);
+        if (table == null) {
+            throw exception(CODEGEN_TABLE_NOT_EXISTS);
+        }
+        String tableSchema = codegenProperties.getDbSchemas().iterator().next();
+        // 从数据库中,获得数据库表结构
+        List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableSchema, table.getTableName());
+
+        // 执行同步
+        this.syncCodegen0(tableId, schemaColumns);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void syncCodegenFromSQL(Long tableId, String sql) {
+        // 校验是否已经存在
+        ToolCodegenTableDO table = codegenTableMapper.selectById(tableId);
+        if (table == null) {
+            throw exception(CODEGEN_TABLE_NOT_EXISTS);
+        }
+        // 从 SQL 中,获得数据库表结构
+        List<ToolSchemaColumnDO> schemaColumns;
+        try {
+            KeyValue<ToolSchemaTableDO, List<ToolSchemaColumnDO>> result = ToolCodegenSQLParser.parse(sql);
+            schemaColumns = result.getValue();
+        } catch (Exception ex) {
+            throw exception(CODEGEN_PARSE_SQL_ERROR);
+        }
+
+        // 执行同步
+        this.syncCodegen0(tableId, schemaColumns);
+    }
+
+    private void syncCodegen0(Long tableId, List<ToolSchemaColumnDO> schemaColumns) {
+        // 校验导入的字段不为空
+        if (CollUtil.isEmpty(schemaColumns)) {
+            throw exception(CODEGEN_SYNC_COLUMNS_NULL);
+        }
+        Set<String> schemaColumnNames = CollectionUtils.convertSet(schemaColumns, ToolSchemaColumnDO::getColumnName);
+
+        // 构建 ToolCodegenColumnDO 数组,只同步新增的字段
+        List<ToolCodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
+        Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, ToolCodegenColumnDO::getColumnName);
+        // 移除已经存在的字段
+        schemaColumns.removeIf(column -> codegenColumnNames.contains(column.getColumnName()));
+        // 计算需要删除的字段
+        Set<Long> deleteColumnIds = codegenColumns.stream().filter(column -> !schemaColumnNames.contains(column.getColumnName()))
+                .map(ToolCodegenColumnDO::getId).collect(Collectors.toSet());
+        if (CollUtil.isEmpty(schemaColumns) && CollUtil.isEmpty(deleteColumnIds)) {
+            throw exception(CODEGEN_SYNC_NONE_CHANGE);
+        }
+
+        // 插入新增的字段
+        List<ToolCodegenColumnDO> columns = codegenBuilder.buildColumns(schemaColumns);
+        columns.forEach(column -> {
+            column.setTableId(tableId);
+            codegenColumnMapper.insert(column); // TODO 批量插入
+        });
+        // 删除不存在的字段
+        if (CollUtil.isNotEmpty(deleteColumnIds)) {
+            codegenColumnMapper.deleteBatchIds(deleteColumnIds);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteCodegen(Long tableId) {
+        // 校验是否已经存在
+        if (codegenTableMapper.selectById(tableId) == null) {
+            throw exception(CODEGEN_TABLE_NOT_EXISTS);
+        }
+
+        // 删除 table 表定义
+        codegenTableMapper.deleteById(tableId);
+        // 删除 column 字段定义
+        codegenColumnMapper.deleteListByTableId(tableId);
+    }
+
+    @Override
+    public PageResult<ToolCodegenTableDO> getCodegenTablePage(ToolCodegenTablePageReqVO pageReqVO) {
+        return codegenTableMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public ToolCodegenTableDO getCodegenTablePage(Long id) {
+        return codegenTableMapper.selectById(id);
+    }
+
+    @Override
+    public List<ToolCodegenTableDO> getCodeGenTableList() {
+        return codegenTableMapper.selectList();
+    }
+
+    @Override
+    public List<ToolCodegenColumnDO> getCodegenColumnListByTableId(Long tableId) {
+        return codegenColumnMapper.selectListByTableId(tableId);
+    }
+
+    @Override
+    public Map<String, String> generationCodes(Long tableId) {
+        // 校验是否已经存在
+        ToolCodegenTableDO table = codegenTableMapper.selectById(tableId);
+        if (codegenTableMapper.selectById(tableId) == null) {
+            throw exception(CODEGEN_TABLE_NOT_EXISTS);
+        }
+        List<ToolCodegenColumnDO> columns = codegenColumnMapper.selectListByTableId(tableId);
+        if (CollUtil.isEmpty(columns)) {
+            throw exception(CODEGEN_COLUMN_NOT_EXISTS);
+        }
+
+        // 执行生成
+        return codegenEngine.execute(table, columns);
+    }
+
+    @Override
+    public List<ToolSchemaTableDO> getSchemaTableList(String tableName, String tableComment) {
+        List<ToolSchemaTableDO> tables = schemaTableMapper.selectList(codegenProperties.getDbSchemas(), tableName, tableComment);
+        // TODO 强制移除 Quartz 的表,未来做成可配置
+        tables.removeIf(table -> table.getTableName().startsWith("QRTZ_"));
+        return tables;
+    }
+
+//    /**
+//     * 修改保存参数校验
+//     *
+//     * @param genTable 业务信息
+//     */
+//    @Override
+//    public void validateEdit(GenTable genTable) {
+//        if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
+//            String options = JSON.toJSONString(genTable.getParams());
+//            JSONObject paramsObj = JSONObject.parseObject(options);
+//            if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) {
+//                throw new CustomException("树编码字段不能为空");
+//            } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) {
+//                throw new CustomException("树父编码字段不能为空");
+//            } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) {
+//                throw new CustomException("树名称字段不能为空");
+//            }
+//        }
+//    }
+
+}

+ 75 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/service/test/ToolTestDemoService.java

@@ -0,0 +1,75 @@
+package com.persagy.modules.tool.service.test;
+
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoCreateReqVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoExportReqVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoPageReqVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoUpdateReqVO;
+import com.persagy.modules.tool.dal.dataobject.test.ToolTestDemoDO;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 字典类型 Service 接口
+ *
+ * @author
+ */
+public interface ToolTestDemoService {
+
+    /**
+     * 创建字典类型
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createTestDemo(@Valid ToolTestDemoCreateReqVO createReqVO);
+
+    /**
+     * 更新字典类型
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateTestDemo(@Valid ToolTestDemoUpdateReqVO updateReqVO);
+
+    /**
+     * 删除字典类型
+     *
+     * @param id 编号
+     */
+    void deleteTestDemo(Long id);
+
+    /**
+     * 获得字典类型
+     *
+     * @param id 编号
+     * @return 字典类型
+     */
+    ToolTestDemoDO getTestDemo(Long id);
+
+    /**
+     * 获得字典类型列表
+     *
+     * @param ids 编号
+     * @return 字典类型列表
+     */
+    List<ToolTestDemoDO> getTestDemoList(Collection<Long> ids);
+
+    /**
+     * 获得字典类型分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 字典类型分页
+     */
+    PageResult<ToolTestDemoDO> getTestDemoPage(ToolTestDemoPageReqVO pageReqVO);
+
+    /**
+     * 获得字典类型列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 字典类型列表
+     */
+    List<ToolTestDemoDO> getTestDemoList(ToolTestDemoExportReqVO exportReqVO);
+
+}

+ 86 - 0
iot-admin-server/src/main/java/com/persagy/modules/tool/service/test/impl/ToolTestDemoServiceImpl.java

@@ -0,0 +1,86 @@
+package com.persagy.modules.tool.service.test.impl;
+
+import com.persagy.framework.common.pojo.PageResult;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoCreateReqVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoExportReqVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoPageReqVO;
+import com.persagy.modules.tool.controller.test.vo.ToolTestDemoUpdateReqVO;
+import com.persagy.modules.tool.convert.test.ToolTestDemoConvert;
+import com.persagy.modules.tool.dal.dataobject.test.ToolTestDemoDO;
+import com.persagy.modules.tool.dal.mysql.test.ToolTestDemoMapper;
+import com.persagy.modules.tool.service.test.ToolTestDemoService;
+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 com.persagy.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.persagy.modules.tool.enums.ToolErrorCodeConstants.TEST_DEMO_NOT_EXISTS;
+
+/**
+ * 字典类型 Service 实现类
+ *
+ * @author
+ */
+@Service
+@Validated
+public class ToolTestDemoServiceImpl implements ToolTestDemoService {
+
+    @Resource
+    private ToolTestDemoMapper testDemoMapper;
+
+    @Override
+    public Long createTestDemo(ToolTestDemoCreateReqVO createReqVO) {
+        // 插入
+        ToolTestDemoDO testDemo = ToolTestDemoConvert.INSTANCE.convert(createReqVO);
+        testDemoMapper.insert(testDemo);
+        // 返回
+        return testDemo.getId();
+    }
+
+    @Override
+    public void updateTestDemo(ToolTestDemoUpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validateTestDemoExists(updateReqVO.getId());
+        // 更新
+        ToolTestDemoDO updateObj = ToolTestDemoConvert.INSTANCE.convert(updateReqVO);
+        testDemoMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteTestDemo(Long id) {
+        // 校验存在
+        this.validateTestDemoExists(id);
+        // 删除
+        testDemoMapper.deleteById(id);
+    }
+
+    private void validateTestDemoExists(Long id) {
+        if (testDemoMapper.selectById(id) == null) {
+            throw exception(TEST_DEMO_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public ToolTestDemoDO getTestDemo(Long id) {
+        return testDemoMapper.selectById(id);
+    }
+
+    @Override
+    public List<ToolTestDemoDO> getTestDemoList(Collection<Long> ids) {
+        return testDemoMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<ToolTestDemoDO> getTestDemoPage(ToolTestDemoPageReqVO pageReqVO) {
+        return testDemoMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<ToolTestDemoDO> getTestDemoList(ToolTestDemoExportReqVO exportReqVO) {
+        return testDemoMapper.selectList(exportReqVO);
+    }
+
+}

+ 131 - 0
iot-admin-server/src/main/resources/application-local.yaml

@@ -0,0 +1,131 @@
+server:
+  port: 48080
+
+--- #################### 数据库相关配置 ####################
+
+spring:
+  # 数据源配置项
+  autoconfigure:
+    exclude:
+      - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
+  datasource:
+    druid: # Druid 【监控】相关的全局配置
+      web-stat-filter:
+        enabled: true
+      stat-view-servlet:
+        enabled: true
+        allow: # 设置白名单,不填则允许所有访问
+        url-pattern: /druid/*
+        login-username: # 控制台管理用户名和密码
+        login-password:
+      filter:
+        stat:
+          enabled: true
+          log-slow-sql: true # 慢 SQL 记录
+          slow-sql-millis: 100
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+    dynamic: # 多数据源配置
+      druid: # Druid 【连接池】相关的全局配置
+        initial-size: 5 # 初始连接数
+        min-idle: 10 # 最小连接池数量
+        max-active: 20 # 最大连接池数量
+        max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
+        time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
+        min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
+        max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
+        validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
+        test-while-idle: true
+        test-on-borrow: false
+        test-on-return: false
+      primary: master
+      datasource:
+        master:
+          name: world
+          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
+          driver-class-name: com.mysql.jdbc.Driver
+          username: root
+          password: 123456
+        slave: # 模拟从库,可根据自己需要修改
+          name: world
+          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
+          driver-class-name: com.mysql.jdbc.Driver
+          username: root
+          password: 123456
+#  # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+#  redis:
+#    host: 192.168.64.15 # 地址
+#    port: 9944 # 端口
+#    password: brzj123456
+#    database: 2 # 数据库索引
+
+--- #################### 定时任务相关配置 ####################
+
+# Quartz 配置项,对应 QuartzProperties 配置类
+spring:
+  quartz:
+    scheduler-name: schedulerName # Scheduler 名字。默认为 schedulerName
+    job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
+    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
+    properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
+      org:
+        quartz:
+          # Scheduler 相关配置
+          scheduler:
+            instanceName: schedulerName
+            instanceId: AUTO # 自动生成 instance ID
+          # JobStore 相关配置
+          jobStore:
+            class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 实现类
+            isClustered: true # 是集群模式
+            clusterCheckinInterval: 15000 # 集群检查频率,单位:毫秒。默认为 15000,即 15 秒
+            misfireThreshold: 60000 # misfire 阀值,单位:毫秒。
+          # 线程池相关配置
+          threadPool:
+            threadCount: 25 # 线程池大小。默认为 10 。
+            threadPriority: 5 # 线程优先级
+            class: org.quartz.simpl.SimpleThreadPool # 线程池类型
+    jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置
+      initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
+--- #################### 监控相关配置 ####################
+
+# Actuator 监控端点的配置项
+management:
+  endpoints:
+    web:
+      base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+      exposure:
+        include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# Spring Boot Admin 配置项
+spring:
+  boot:
+    admin:
+      # Spring Boot Admin Client 客户端的相关配置
+      client:
+        url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址
+        instance:
+          prefer-ip: true # 注册实例时,优先使用 IP
+      # Spring Boot Admin Server 服务端的相关配置
+      context-path: /admin # 配置 Spring
+
+# 日志文件配置
+logging:
+  file:
+    name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
+
+--- #################### BDTP相关配置 ####################
+
+# BDTP配置项,设置当前项目所有自定义的配置
+biz:
+  file:
+    base-path: http://127.0.0.1:${server.port}${biz.web.api-prefix}/infra/file/get/
+  xss:
+    enable: false
+    exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
+      - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
+      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
+  demo: false # 关闭演示模式
+

+ 62 - 0
iot-admin-server/src/main/resources/application.yaml

@@ -0,0 +1,62 @@
+spring:
+  application:
+    name: iot-admin-server
+
+  profiles:
+    active: local
+
+  # Servlet 配置
+  servlet:
+    # 文件上传相关配置项
+    multipart:
+      max-file-size: 16MB # 单个文件大小
+      max-request-size: 32MB # 设置总上传的文件大小
+
+  # Jackson 配置项
+  jackson:
+    serialization:
+      write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
+      write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
+      write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
+      fail-on-empty-beans: false # 允许序列化无属性的 Bean
+
+# MyBatis Plus 的配置项
+mybatis-plus:
+  configuration:
+    map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印日志
+  global-config:
+    db-config:
+      id-type: AUTO # 自增 ID
+      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
+      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+  mapper-locations: classpath*:mapper/*.xml
+  type-aliases-package: ${biz.info.base-package}.modules.*.dal.dataobject
+
+--- #################### BDTP相关配置 ####################
+
+biz:
+  info:
+    version: 1.0.0
+    base-package: com.persagy
+  web:
+    api-prefix: /api
+    controller-package: ${biz.info.base-package}
+  swagger:
+    enable: true
+    title: 管理后台
+    description: 提供管理员管理的所有功能
+    version: ${biz.info.version}
+    base-package: ${biz.info.base-package}.modules
+  codegen:
+    base-package: ${biz.info.base-package}
+    db-schemas: ${spring.datasource.dynamic.datasource.master.name}
+  error-code: # 错误码相关配置项
+    constants-class-list:
+      - com.persagy.modules.infra.enums.InfErrorCodeConstants
+      - com.persagy.modules.infra.enums.SysErrorCodeConstants
+debug: true
+management:
+  health:
+    redis:
+      enabled: false #解决actuator检查redis报错问题

+ 15 - 0
iot-admin-server/src/main/resources/banner.txt

@@ -0,0 +1,15 @@
+Spring Boot Version: ${spring-boot.version}
+
+.__   __.   ______      .______    __    __    _______
+|  \ |  |  /  __  \     |   _  \  |  |  |  |  /  _____|
+|   \|  | |  |  |  |    |  |_)  | |  |  |  | |  |  __
+|  . `  | |  |  |  |    |   _  <  |  |  |  | |  | |_ |
+|  |\   | |  `--'  |    |  |_)  | |  `--'  | |  |__| |
+|__| \__|  \______/     |______/   \______/   \______|
+
+███╗   ██╗ ██████╗     ██████╗ ██╗   ██╗ ██████╗
+████╗  ██║██╔═══██╗    ██╔══██╗██║   ██║██╔════╝
+██╔██╗ ██║██║   ██║    ██████╔╝██║   ██║██║  ███╗
+██║╚██╗██║██║   ██║    ██╔══██╗██║   ██║██║   ██║
+██║ ╚████║╚██████╔╝    ██████╔╝╚██████╔╝╚██████╔╝
+╚═╝  ╚═══╝ ╚═════╝     ╚═════╝  ╚═════╝  ╚═════╝

+ 92 - 0
iot-admin-server/src/main/resources/codegen/java/controller/controller.vm

@@ -0,0 +1,92 @@
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName};
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import ${PageResultClassName};
+import ${CommonResultClassName};
+import static ${CommonResultClassName}.success;
+
+import ${ExcelUtilsClassName};
+
+import ${OperateLogClassName};
+import static ${OperateTypeEnumClassName}.*;
+
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.modules.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
+import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.${table.className}Service;
+
+@Api(tags = "${table.classComment}")
+@RestController
+##二级的 businessName 暂时不算在 HTTP 路径上,可以根据需要写
+@RequestMapping("/${table.moduleName}/${simpleClassName_strikeCase}")
+@Validated
+public class ${table.className}Controller {
+
+    @Resource
+    private ${table.className}Service ${classNameVar}Service;
+
+    @PostMapping("/create")
+    @ApiOperation("创建${table.classComment}")
+    public CommonResult<${primaryColumn.javaType}> create${simpleClassName}(@Valid @RequestBody ${table.className}CreateReqVO createReqVO) {
+        return success(${classNameVar}Service.create${simpleClassName}(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新${table.classComment}")
+    public CommonResult<Boolean> update${simpleClassName}(@Valid @RequestBody ${table.className}UpdateReqVO updateReqVO) {
+        ${classNameVar}Service.update${simpleClassName}(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除${table.classComment}")
+    @ApiImplicitParam(name = "id", value = "编号", required = true)
+    public CommonResult<Boolean> delete${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
+        ${classNameVar}Service.delete${simpleClassName}(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得${table.classComment}")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = ${primaryColumn.javaType}.class)
+    public CommonResult<${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")
+    @ApiOperation("获得${table.classComment}列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+    public CommonResult<List<${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));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得${table.classComment}分页")
+    public CommonResult<PageResult<${table.className}RespVO>> get${simpleClassName}Page(@Valid ${table.className}PageReqVO pageVO) {
+        PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageVO);
+        return success(${table.className}Convert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出${table.classComment} Excel")
+    @OperateLog(type = EXPORT)
+    public void export${simpleClassName}Excel(@Valid ${table.className}ExportReqVO exportReqVO,
+              HttpServletResponse response) throws IOException {
+        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(exportReqVO);
+        // 导出 Excel
+        List<${table.className}ExcelVO> datas = ${table.className}Convert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${table.className}ExcelVO.class, datas);
+    }
+
+}

+ 13 - 0
iot-admin-server/src/main/resources/codegen/java/controller/vo/_column.vm

@@ -0,0 +1,13 @@
+## 提供给 baseVO、createVO、updateVO 生成字段
+    @ApiModelProperty(value = "${column.columnComment}"#if (!${column.nullable}), required = true#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} == "Date")## 时间类型
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+#end
+    private ${column.javaType} ${column.javaField};

+ 31 - 0
iot-admin-server/src/main/resources/codegen/java/controller/vo/baseVO.vm

@@ -0,0 +1,31 @@
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+## 处理 Date 字段的引入
+#foreach ($column in $columns)
+#if (${column.createOperation} && ${column.updateOperation} && ${column.listOperationResult}
+    && ${column.javaType} == "Date")## 时间类型
+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 ${table.className}BaseVO {
+
+#foreach ($column in $columns)
+#if (${column.createOperation} && ${column.updateOperation} && ${column.listOperationResult})##通用操作
+    #parse("codegen/java/controller/vo/_column.vm")
+
+#end
+#end
+}

+ 30 - 0
iot-admin-server/src/main/resources/codegen/java/controller/vo/createReqVO.vm

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

+ 37 - 0
iot-admin-server/src/main/resources/codegen/java/controller/vo/excelVO.vm

@@ -0,0 +1,37 @@
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+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 ${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
+}

+ 42 - 0
iot-admin-server/src/main/resources/codegen/java/controller/vo/exportReqVO.vm

@@ -0,0 +1,42 @@
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import ${PageParamClassName};
+## 处理 Date 字段的引入
+#foreach ($column in $columns)
+#if (${column.listOperation} && ${column.javaType} == "Date")## 时间类型
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+#break
+#end
+#end
+## 字段模板
+#macro(columnTpl $prefix $prefixStr)
+#if (${column.javaType} == "Date")## 时间类型
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+#end
+    @ApiModelProperty(value = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
+    private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
+#end
+
+@ApiModel(value = "${table.classComment} Excel 导出 Request VO", description = "参数和 ${table.className}PageReqVO 是一致的")
+@Data
+public class ${table.className}ExportReqVO {
+
+#foreach ($column in $columns)
+#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+#if (${column.listOperation})##查询操作
+#if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候
+    #columnTpl('begin', '开始')
+
+    #columnTpl('end', '结束')
+#else##情况二,非 Between 的时间
+    #columnTpl('', '')
+#end
+
+#end
+#end
+}

+ 44 - 0
iot-admin-server/src/main/resources/codegen/java/controller/vo/pageReqVO.vm

@@ -0,0 +1,44 @@
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import ${PageParamClassName};
+## 处理 Date 字段的引入
+#foreach ($column in $columns)
+#if (${column.listOperation} && ${column.javaType} == "Date")## 时间类型
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+#break
+#end
+#end
+## 字段模板
+#macro(columnTpl $prefix $prefixStr)
+#if (${column.javaType} == "Date")## 时间类型
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+#end
+    @ApiModelProperty(value = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
+    private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
+#end
+
+@ApiModel("${table.classComment}分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ${table.className}PageReqVO extends PageParam {
+
+#foreach ($column in $columns)
+#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+#if (${column.listOperation})##查询操作
+#if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候
+    #columnTpl('begin', '开始')
+
+    #columnTpl('end', '结束')
+#else##情况二,非 Between 的时间
+    #columnTpl('', '')
+#end
+
+#end
+#end
+}

+ 20 - 0
iot-admin-server/src/main/resources/codegen/java/controller/vo/respVO.vm

@@ -0,0 +1,20 @@
+package ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("${table.classComment} Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ${table.className}RespVO extends ${table.className}BaseVO {
+
+#foreach ($column in $columns)
+#if (${column.listOperationResult} && (!${column.createOperation} || !${column.updateOperation}))##不是通用字段
+    @ApiModelProperty(value = "${column.columnComment}"#if (!${column.nullable}), required = true#end#if ("$!column.example" != ""), example = "${column.example}"#end)
+    private ${column.javaType} ${column.javaField};
+
+#end
+#end
+}

+ 30 - 0
iot-admin-server/src/main/resources/codegen/java/controller/vo/updateReqVO.vm

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

+ 34 - 0
iot-admin-server/src/main/resources/codegen/java/convert/convert.vm

@@ -0,0 +1,34 @@
+package ${basePackage}.modules.${table.moduleName}.convert.${table.businessName};
+
+import java.util.*;
+
+import ${PageResultClassName};
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${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(${table.className}CreateReqVO bean);
+
+    ${table.className}DO convert(${table.className}UpdateReqVO bean);
+
+    ${table.className}RespVO convert(${table.className}DO bean);
+
+    List<${table.className}RespVO> convertList(List<${table.className}DO> list);
+
+    PageResult<${table.className}RespVO> convertPage(PageResult<${table.className}DO> page);
+
+    List<${table.className}ExcelVO> convertList02(List<${table.className}DO> list);
+
+}

+ 38 - 0
iot-admin-server/src/main/resources/codegen/java/dal/do.vm

@@ -0,0 +1,38 @@
+package ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName};
+
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import ${BaseDOClassName};
+
+/**
+ * ${table.classComment} DO
+ *
+ * @author ${table.author}
+ */
+@TableName("${table.tableName}")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ${table.className}DO extends BaseDO {
+
+#foreach ($column in $columns)
+#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
+
+}

+ 66 - 0
iot-admin-server/src/main/resources/codegen/java/dal/mapper.vm

@@ -0,0 +1,66 @@
+package ${basePackage}.modules.${table.moduleName}.dal.mysql.${table.businessName};
+
+import java.util.*;
+
+import ${PageResultClassName};
+import ${QueryWrapperClassName};
+import ${BaseMapperClassName};
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import org.apache.ibatis.annotations.Mapper;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+
+## 字段模板
+#macro(listCondition)
+#foreach ($column in $columns)
+#if (${column.listOperation})
+#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+#if (${column.listOperationCondition} == "=")##情况一,= 的时候
+                .eqIfPresent("${column.columnName}", reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "!=")##情况二,!= 的时候
+                .neIfPresent("${column.columnName}", reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == ">")##情况三,> 的时候
+                .gtIfPresent("${column.columnName}", reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == ">=")##情况四,>= 的时候
+                .geIfPresent("${column.columnName}", reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "<")##情况五,< 的时候
+                .ltIfPresent("${column.columnName}", reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "<=")##情况五,<= 的时候
+                .leIfPresent("${column.columnName}", reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "LIKE")##情况七,Like 的时候
+                .likeIfPresent("${column.columnName}", reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "BETWEEN")##情况八,Between 的时候
+                .betweenIfPresent("${column.columnName}", reqVO.getBegin${JavaField}(), reqVO.getEnd${JavaField}())
+#end
+#end
+#end
+#end
+/**
+ * ${table.classComment} Mapper
+ *
+ * @author ${table.author}
+ */
+@Mapper
+public interface ${table.className}Mapper extends BaseMapperX<${table.className}DO> {
+
+    default PageResult<${table.className}DO> selectPage(${table.className}PageReqVO reqVO) {
+        return selectPage(reqVO, new QueryWrapperX<${table.className}DO>()
+			#listCondition()
+                .orderByDesc("id")## 大多数情况下,id 倒序
+        );
+    }
+
+    default List<${table.className}DO> selectList(${table.className}ExportReqVO reqVO) {
+        return selectList(new QueryWrapperX<${table.className}DO>()
+			#listCondition()
+                .orderByDesc("id")## 大多数情况下,id 倒序
+        );
+    }
+
+}

+ 2 - 0
iot-admin-server/src/main/resources/codegen/java/enums/errorcode.vm

@@ -0,0 +1,2 @@
+// ========== ${table.classComment} TODO 补充编号 ==========
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${table.classComment}不存在");

+ 70 - 0
iot-admin-server/src/main/resources/codegen/java/service/service.vm

@@ -0,0 +1,70 @@
+package ${basePackage}.modules.${table.moduleName}.service.${table.businessName};
+
+import java.util.*;
+import javax.validation.*;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${PageResultClassName};
+
+/**
+ * ${table.classComment} Service 接口
+ *
+ * @author ${table.author}
+ */
+public interface ${table.className}Service {
+
+    /**
+     * 创建${table.classComment}
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    ${primaryColumn.javaType} create${simpleClassName}(@Valid ${table.className}CreateReqVO createReqVO);
+
+    /**
+     * 更新${table.classComment}
+     *
+     * @param updateReqVO 更新信息
+     */
+    void update${simpleClassName}(@Valid ${table.className}UpdateReqVO updateReqVO);
+
+    /**
+     * 删除${table.classComment}
+     *
+     * @param id 编号
+     */
+    void delete${simpleClassName}(${primaryColumn.javaType} id);
+
+    /**
+     * 获得${table.classComment}
+     *
+     * @param id 编号
+     * @return ${table.classComment}
+     */
+    ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id);
+
+    /**
+     * 获得${table.classComment}列表
+     *
+     * @param ids 编号
+     * @return ${table.classComment}列表
+     */
+    List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids);
+
+    /**
+     * 获得${table.classComment}分页
+     *
+     * @param pageReqVO 分页查询
+     * @return ${table.classComment}分页
+     */
+    PageResult<${table.className}DO> get${simpleClassName}Page(${table.className}PageReqVO pageReqVO);
+
+    /**
+     * 获得${table.classComment}列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return ${table.classComment}列表
+     */
+    List<${table.className}DO> get${simpleClassName}List(${table.className}ExportReqVO exportReqVO);
+
+}

+ 83 - 0
iot-admin-server/src/main/resources/codegen/java/service/serviceImpl.vm

@@ -0,0 +1,83 @@
+package ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.impl;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${PageResultClassName};
+
+import ${basePackage}.modules.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
+import ${basePackage}.modules.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
+import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.${table.className}Service;
+
+import static ${ServiceExceptionUtilClassName}.exception;
+import static ${basePackage}.modules.${table.moduleName}.enums.${simpleModuleName_upperFirst}ErrorCodeConstants.*;
+
+/**
+ * ${table.classComment} Service 实现类
+ *
+ * @author ${table.author}
+ */
+@Service
+@Validated
+public class ${table.className}ServiceImpl implements ${table.className}Service {
+
+    @Resource
+    private ${table.className}Mapper ${classNameVar}Mapper;
+
+    @Override
+    public ${primaryColumn.javaType} create${simpleClassName}(${table.className}CreateReqVO createReqVO) {
+        // 插入
+        ${table.className}DO ${classNameVar} = ${table.className}Convert.INSTANCE.convert(createReqVO);
+        ${classNameVar}Mapper.insert(${classNameVar});
+        // 返回
+        return ${classNameVar}.getId();
+    }
+
+    @Override
+    public void update${simpleClassName}(${table.className}UpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validate${simpleClassName}Exists(updateReqVO.getId());
+        // 更新
+        ${table.className}DO updateObj = ${table.className}Convert.INSTANCE.convert(updateReqVO);
+        ${classNameVar}Mapper.updateById(updateObj);
+    }
+
+    @Override
+    public void delete${simpleClassName}(${primaryColumn.javaType} id) {
+        // 校验存在
+        this.validate${simpleClassName}Exists(id);
+        // 删除
+        ${classNameVar}Mapper.deleteById(id);
+    }
+
+    private void validate${simpleClassName}Exists(${primaryColumn.javaType} id) {
+        if (${classNameVar}Mapper.selectById(id) == null) {
+            throw exception(${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id) {
+        return ${classNameVar}Mapper.selectById(id);
+    }
+
+    @Override
+    public List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids) {
+        return ${classNameVar}Mapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<${table.className}DO> get${simpleClassName}Page(${table.className}PageReqVO pageReqVO) {
+        return ${classNameVar}Mapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<${table.className}DO> get${simpleClassName}List(${table.className}ExportReqVO exportReqVO) {
+        return ${classNameVar}Mapper.selectList(exportReqVO);
+    }
+
+}

+ 161 - 0
iot-admin-server/src/main/resources/codegen/java/test/serviceTest.vm

@@ -0,0 +1,161 @@
+package ${basePackage}.modules.${table.moduleName}.service.${table.businessName};
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import ${basePackage}.BaseDbUnitTest;
+import ${basePackage}.modules.${table.moduleName}.service.${table.businessName}.impl.${table.className}ServiceImpl;
+import ${basePackage}.modules.${table.moduleName}.controller.${table.businessName}.vo.*;
+import ${basePackage}.modules.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.modules.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
+import ${basePackage}.util.object.ObjectUtils;
+import ${PageResultClassName};
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static ${basePackage}.modules.${table.moduleName}.enums.${simpleModuleName_upperFirst}ErrorCodeConstants.*;
+import static ${basePackage}.util.AssertUtils.*;
+import static ${basePackage}.util.RandomUtils.*;
+import static ${basePackage}.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+## 字段模板
+#macro(getPageCondition $VO)
+       // mock 数据
+       ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class, o -> { // 等会查询到
+       #foreach ($column in $columns)
+       #if (${column.listOperation})
+       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+           o.set$JavaField(null);
+       #end
+       #end
+       });
+       ${classNameVar}Mapper.insert(db${simpleClassName});
+       #foreach ($column in $columns)
+       #if (${column.listOperation})
+       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+       // 测试 ${column.javaField} 不匹配
+       ${classNameVar}Mapper.insert(ObjectUtils.clone(db${simpleClassName}, o -> o.set$JavaField(null)));
+       #end
+       #end
+       // 准备参数
+       ${table.className}${VO} reqVO = new ${table.className}${VO}();
+       #foreach ($column in $columns)
+       #if (${column.listOperation})
+       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+       #if (${column.listOperationCondition} == "BETWEEN")## BETWEEN 的情况
+       reqVO.setBegin${JavaField}(null);
+       reqVO.setEnd${JavaField}(null);
+       #else
+       reqVO.set$JavaField(null);
+       #end
+       #end
+       #end
+#end
+/**
+* {@link ${table.className}ServiceImpl} 的单元测试类
+*
+* @author ${table.author}
+*/
+@Import(${table.className}ServiceImpl.class)
+public class ${table.className}ServiceTest extends BaseDbUnitTest {
+
+    @Resource
+    private ${table.className}ServiceImpl ${classNameVar}Service;
+
+    @Resource
+    private ${table.className}Mapper ${classNameVar}Mapper;
+
+    @Test
+    public void testCreate${simpleClassName}_success() {
+        // 准备参数
+        ${table.className}CreateReqVO reqVO = randomPojo(${table.className}CreateReqVO.class);
+
+        // 调用
+        ${primaryColumn.javaType} ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(reqVO);
+        // 断言
+        assertNotNull(${classNameVar}Id);
+        // 校验记录的属性是否正确
+        ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(${classNameVar}Id);
+        assertPojoEquals(reqVO, ${classNameVar});
+    }
+
+    @Test
+    public void testUpdate${simpleClassName}_success() {
+        // mock 数据
+        ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
+        ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        ${table.className}UpdateReqVO reqVO = randomPojo(${table.className}UpdateReqVO.class, o -> {
+            o.setId(db${simpleClassName}.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        ${classNameVar}Service.update${simpleClassName}(reqVO);
+        // 校验是否更新正确
+        ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, ${classNameVar});
+    }
+
+    @Test
+    public void testUpdate${simpleClassName}_notExists() {
+        // 准备参数
+        ${table.className}UpdateReqVO reqVO = randomPojo(${table.className}UpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> ${classNameVar}Service.update${simpleClassName}(reqVO), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDelete${simpleClassName}_success() {
+        // mock 数据
+        ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
+        ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        ${primaryColumn.javaType} id = db${simpleClassName}.getId();
+
+        // 调用
+        ${classNameVar}Service.delete${simpleClassName}(id);
+       // 校验数据不存在了
+       assertNull(${classNameVar}Mapper.selectById(id));
+    }
+
+    @Test
+    public void testDelete${simpleClassName}_notExists() {
+        // 准备参数
+        ${primaryColumn.javaType} id = random${primaryColumn.javaType}Id();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
+    }
+
+    @Test // TODO 请修改 null 为需要的值
+    public void testGet${simpleClassName}Page() {
+       #getPageCondition("PageReqVO")
+
+       // 调用
+       PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(db${simpleClassName}, pageResult.getList().get(0));
+    }
+
+    @Test // TODO 请修改 null 为需要的值
+    public void testGet${simpleClassName}List() {
+       #getPageCondition("ExportReqVO")
+
+       // 调用
+       List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(reqVO);
+       // 断言
+       assertEquals(1, list.size());
+       assertPojoEquals(db${simpleClassName}, list.get(0));
+    }
+
+}

+ 27 - 0
iot-admin-server/src/main/resources/codegen/sql/sql.vm

@@ -0,0 +1,27 @@
+-- 菜单 SQL
+INSERT INTO `sys_menu`(
+    `name`, `permission`, `menu_type`, `sort`, `parent_id`,
+    `path`, `icon`, `component`, `status`
+)
+VALUES (
+    '${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
+    '${simpleClassName_strikeCase}', '', '${table.moduleName}/${classNameVar}/index', 0
+);
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+#set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])
+#set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])
+#foreach ($functionName in $functionNames)
+#set ($index = $foreach.count - 1)
+INSERT INTO `sys_menu`(
+    `name`, `permission`, `menu_type`, `sort`, `parent_id`,
+    `path`, `icon`, `component`, `status`
+)
+VALUES (
+    '${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId,
+    '', '', '', 0
+);
+#end

+ 55 - 0
iot-admin-server/src/main/resources/codegen/vue/api/api.js.vm

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+// 创建${table.classComment}
+export function create${simpleClassName}(data) {
+  return request({
+    url: '${baseURL}/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新${table.classComment}
+export function update${simpleClassName}(data) {
+  return request({
+    url: '${baseURL}/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除${table.classComment}
+export function delete${simpleClassName}(id) {
+  return request({
+    url: '${baseURL}/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得${table.classComment}
+export function get${simpleClassName}(id) {
+  return request({
+    url: '${baseURL}/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得${table.classComment}分页
+export function get${simpleClassName}Page(query) {
+  return request({
+    url: '${baseURL}/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出${table.classComment} Excel
+export function export${simpleClassName}Excel(query) {
+  return request({
+    url: '${baseURL}/export-excel',
+    method: 'get',
+    params: query,
+    responseType: 'blob'
+  })
+}

+ 403 - 0
iot-admin-server/src/main/resources/codegen/vue/views/index.vue.vm

@@ -0,0 +1,403 @@
+<template>
+  <div class="app-container">
+
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if ($column.listOperation)
+    #set ($dictType=$column.dictType)
+    #set ($javaField = $column.javaField)
+    #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+    #set ($comment=$column.columnComment)
+#if ($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${javaField}">
+        <el-input v-model="queryParams.${javaField}" placeholder="请输入${comment}" clearable size="small" @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+#elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+      <el-form-item label="${comment}" prop="${javaField}">
+        <el-select v-model="queryParams.${javaField}" placeholder="请选择${comment}" clearable size="small">
+    #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
+                       :key="dict.value" :label="dict.label" :value="dict.value"/>
+    #else## 未设置 dictType 数据字典的情况
+          <el-option label="请选择字典生成" value="" />
+    #end
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime")
+    #if ($column.listOperationCondition != "BETWEEN")## 非范围
+      <el-form-item label="${comment}" prop="${javaField}">
+        <el-date-picker clearable size="small" v-model="queryParams.${javaField}" type="date" value-format="yyyy-MM-dd" placeholder="选择${comment}" />
+      </el-form-item>
+    #else## 范围
+      <el-form-item label="${comment}">
+        <el-date-picker v-model="dateRange${AttrName}" size="small" style="width: 240px" value-format="yyyy-MM-dd"
+                        type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
+      </el-form-item>
+    #end
+#end
+#end
+#end
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
+                   v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+#foreach($column in $columns)
+#if ($column.listOperationResult)
+    #set ($dictType=$column.dictType)
+    #set ($javaField = $column.javaField)
+    #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+    #set ($comment=$column.columnComment)
+#if ($column.javaType == "Date")## 时间类型
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.${javaField}) }}</span>
+        </template>
+      </el-table-column>
+#elseif("" != $column.dictType)## 数据字典
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template slot-scope="scope">
+          <span>{{ getDictDataLabel(DICT_TYPE.$dictType.toUpperCase(), scope.row.${column.javaField}) }}</span>
+        </template>
+      </el-table-column>
+#else
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+                     v-hasPermi="['${permissionPrefix}:update']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['${permissionPrefix}:delete']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#if ($column.createOperation || $column.updateOperation)
+    #set ($dictType = $column.dictType)
+    #set ($javaField = $column.javaField)
+    #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+    #set ($comment = $column.columnComment)
+#if ($column.htmlType == "input")
+  #if (!$column.primaryKey)## 忽略主键,不用在表单里
+        <el-form-item label="${comment}" prop="${javaField}">
+          <el-input v-model="form.${javaField}" placeholder="请输入${comment}" />
+        </el-form-item>
+  #end
+#elseif($column.htmlType == "imageUpload")## 图片上传
+        #set ($hasImageUploadColumn = true)
+        <el-form-item label="${comment}">
+          <imageUpload v-model="form.${javaField}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")## 文件上传
+        #set ($hasFileUploadColumn = true)
+        <el-form-item label="${comment}">
+          <fileUpload v-model="form.${javaField}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")## 文本编辑器
+        #set ($hasEditorColumn = true)
+        <el-form-item label="${comment}">
+          <editor v-model="form.${javaField}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select")## 下拉框
+        <el-form-item label="${comment}" prop="${javaField}">
+          <el-select v-model="form.${javaField}" placeholder="请选择${comment}">
+    #if ("" != $dictType)## 有数据字典
+            <el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
+                       :key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
+    #else##没数据字典
+            <el-option label="请选择字典生成" value="" />
+    #end
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox")## 多选框
+        <el-form-item label="${comment}" prop="${javaField}">
+          <el-checkbox-group v-model="form.${javaField}">
+    #if ("" != $dictType)## 有数据字典
+            <el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
+                         :key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
+    #else##没数据字典
+            <el-checkbox>请选择字典生成</el-checkbox>
+    #end
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio")## 单选框
+        <el-form-item label="${comment}" prop="${javaField}">
+          <el-radio-group v-model="form.${javaField}">
+    #if ("" != $dictType)## 有数据字典
+            <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
+                      :key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-radio>
+    #else##没数据字典
+            <el-radio label="1">请选择字典生成</el-radio>
+    #end
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")## 时间框
+        <el-form-item label="${comment}" prop="${javaField}">
+          <el-date-picker clearable size="small" v-model="form.${javaField}" type="date" value-format="yyyy-MM-dd" placeholder="选择${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "textarea")## 文本框
+        <el-form-item label="${comment}" prop="${javaField}">
+          <el-input v-model="form.${javaField}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { create${simpleClassName}, update${simpleClassName}, delete${simpleClassName}, get${simpleClassName}, get${simpleClassName}Page, export${simpleClassName}Excel } from "@/api/${table.moduleName}/${classNameVar}";
+#if ($hasImageUploadColumn)
+import ImageUpload from '@/components/ImageUpload';
+#end
+#if ($hasFileUploadColumn)
+import FileUpload from '@/components/FileUpload';
+#end
+#if ($hasEditorColumn)
+import Editor from '@/components/Editor';
+#end
+
+export default {
+  name: "${simpleClassName}",
+  components: {
+#if ($hasImageUploadColumn)
+    ImageUpload,
+#end
+#if ($hasFileUploadColumn)
+    FileUpload,
+#end
+#if ($hasEditorColumn)
+    Editor,
+#end
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // ${table.classComment}列表
+      list: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      #foreach ($column in $columns)## 时间范围
+      #if ($column.listOperation)
+      #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
+      #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      dateRange${AttrName}: [],
+      #end
+      #end
+      #end
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        #foreach ($column in $columns)
+        #if ($column.listOperation && $column.listOperationCondition != 'BETWEEN')
+        $column.javaField: null,
+        #end
+        #end
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      #foreach ($column in $columns)
+      #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+        #set($comment=$column.columnComment)
+        $column.javaField: [{ required: true, message: "${comment}不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }],
+      #end
+      #end
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 处理查询参数
+      let params = {...this.queryParams};
+      #foreach ($column in $columns)
+      #if ($column.listOperation)
+      #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
+      #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      this.addBeginAndEndTime(params, this.dateRange${AttrName}, '${column.javaField}');
+      #end
+      #end
+      #end
+      // 执行查询
+      get${simpleClassName}Page(params).then(response => {
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        #foreach ($column in $columns)
+        #if ($column.createOperation || $column.updateOperation)
+        #if ($column.htmlType == "checkbox")
+        $column.javaField: [],
+        #else
+        $column.javaField: undefined,
+        #end
+        #end
+        #end
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      #foreach ($column in $columns)
+      #if ($column.listOperation)
+      #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
+      #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      this.dateRange${AttrName} = [];
+      #end
+      #end
+      #end
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加${table.classComment}";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
+      get${simpleClassName}(${primaryColumn.javaField}).then(response => {
+        this.form = response.data;
+        #foreach ($column in $columns)
+        #if($column.htmlType == "checkbox")## checkbox 特殊处理
+        this.form.$column.javaField = this.form.${column.javaField}.split(",");
+        #end
+        #end
+        this.open = true;
+        this.title = "修改${table.classComment}";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.#[[$]]#refs["form"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+        #foreach ($column in $columns)
+        #if($column.htmlType == "checkbox")
+        this.form.$column.javaField = this.form.${column.javaField}.join(",");
+        #end
+        #end
+        // 修改的提交
+        if (this.form.${primaryColumn.javaField} != null) {
+          update${simpleClassName}(this.form).then(response => {
+            this.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          });
+          return;
+        }
+        // 添加的提交
+        create${simpleClassName}(this.form).then(response => {
+          this.msgSuccess("新增成功");
+          this.open = false;
+          this.getList();
+        });
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
+      this.$confirm('是否确认删除${table.classComment}编号为"' + ${primaryColumn.javaField} + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delete${simpleClassName}(${primaryColumn.javaField});
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        })
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      // 处理查询参数
+      let params = {...this.queryParams};
+      params.pageNo = undefined;
+      params.pageSize = undefined;
+      #foreach ($column in $columns)
+      #if ($column.listOperation)
+      #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
+      #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      this.addBeginAndEndTime(params, this.dateRange${AttrName}, '${column.javaField}');
+      #end
+      #end
+      #end
+      // 执行导出
+      this.$confirm('是否确认导出所有${table.classComment}数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return export${simpleClassName}Excel(params);
+        }).then(response => {
+          this.downloadExcel(response, '${table.classComment}.xls');
+        })
+    }
+  }
+};
+</script>

+ 16 - 0
iot-admin-server/src/main/resources/rebel.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  This is the JRebel configuration file. It maps the running application to your IDE workspace, enabling JRebel reloading for this project.
+  Refer to https://manuals.jrebel.com/jrebel/standalone/config.html for more information.
+-->
+<application generated-by="intellij" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.zeroturnaround.com" xsi:schemaLocation="http://www.zeroturnaround.com http://update.zeroturnaround.com/jrebel/rebel-2_3.xsd">
+
+	<id>iot-admin-server</id>
+
+	<classpath>
+		<dir name="D:/workspace/iot/iot-admin-server/target/classes">
+		</dir>
+	</classpath>
+
+</application>

+ 139 - 0
iot-common/pom.xml

@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.persagy</groupId>
+        <artifactId>iot</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>iot-common</artifactId>
+    <packaging>jar</packaging>
+
+    <name>  ${project.artifactId}</name>
+    <description>定义基础 pojo 类、枚举、工具类等等</description>
+    <properties>
+<!--        <revision>1.0.0</revision>-->
+        <!-- Maven 相关 -->
+        <java.version>1.8</java.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+
+        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
+        <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
+        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
+    </properties>
+    <dependencies>
+        <!-- Spring 核心 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-expression</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aop</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+
+        <dependency>
+            <!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
+        </dependency>
+
+        <!-- 监控相关 -->
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>apm-toolkit-trace</artifactId>
+        </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct-processor</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
+        </dependency>
+
+        <dependency>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
+        </dependency>
+    </dependencies>
+
+</project>

+ 15 - 0
iot-common/src/main/java/com.persagy/framework/common/core/IntArrayValuable.java

@@ -0,0 +1,15 @@
+package com.persagy.framework.common.core;
+
+/**
+ * 可生成 Int 数组的接口
+ *
+ * @author  
+ */
+public interface IntArrayValuable {
+
+    /**
+     * @return int 数组
+     */
+    int[] array();
+
+}

+ 20 - 0
iot-common/src/main/java/com.persagy/framework/common/core/KeyValue.java

@@ -0,0 +1,20 @@
+package com.persagy.framework.common.core;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * Key Value 的键值对
+ *
+ * @author  
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class KeyValue<K, V> {
+
+    private K key;
+    private V value;
+
+}

+ 32 - 0
iot-common/src/main/java/com.persagy/framework/common/exception/ErrorCode.java

@@ -0,0 +1,32 @@
+package com.persagy.framework.common.exception;
+
+import com.persagy.framework.common.exception.enums.GlobalErrorCodeConstants;
+import com.persagy.framework.common.exception.enums.ServiceErrorCodeRange;
+import lombok.Data;
+
+/**
+ * 错误码对象
+ *
+ * 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
+ * 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
+ *
+ * TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
+ */
+@Data
+public class ErrorCode {
+
+    /**
+     * 错误码
+     */
+    private final Integer code;
+    /**
+     * 错误提示
+     */
+    private final String msg;
+
+    public ErrorCode(Integer code, String message) {
+        this.code = code;
+        this.msg = message;
+    }
+
+}

+ 60 - 0
iot-common/src/main/java/com.persagy/framework/common/exception/ServiceException.java

@@ -0,0 +1,60 @@
+package com.persagy.framework.common.exception;
+
+import com.persagy.framework.common.exception.enums.ServiceErrorCodeRange;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 业务逻辑异常 Exception
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public final class ServiceException extends RuntimeException {
+
+    /**
+     * 业务错误码
+     *
+     * @see ServiceErrorCodeRange
+     */
+    private Integer code;
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public ServiceException() {
+    }
+
+    public ServiceException(ErrorCode errorCode) {
+        this.code = errorCode.getCode();
+        this.message = errorCode.getMsg();
+    }
+
+    public ServiceException(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public ServiceException setCode(Integer code) {
+        this.code = code;
+        return this;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public ServiceException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+}

+ 44 - 0
iot-common/src/main/java/com.persagy/framework/common/exception/enums/GlobalErrorCodeConstants.java

@@ -0,0 +1,44 @@
+package com.persagy.framework.common.exception.enums;
+
+import com.persagy.framework.common.exception.ErrorCode;
+
+/**
+ * 全局错误码枚举
+ * 0-999 系统异常编码保留
+ *
+ * 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
+ * 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
+ * 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
+ *
+ * @author  
+ */
+public interface GlobalErrorCodeConstants {
+
+    ErrorCode SUCCESS = new ErrorCode(0, "成功");
+
+    // ========== 客户端错误段 ==========
+
+    ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
+    ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
+    ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
+    ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
+    ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
+    ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
+    ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
+
+    // ========== 服务端错误段 ==========
+
+    ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
+
+    // ========== 自定义错误段 ==========
+    ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
+    ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
+
+    ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
+
+   static boolean isMatch(Integer code) {
+       return code != null
+               && code >= SUCCESS.getCode() && code <= UNKNOWN.getCode();
+   }
+
+}

+ 34 - 0
iot-common/src/main/java/com.persagy/framework/common/exception/enums/ServiceErrorCodeRange.java

@@ -0,0 +1,34 @@
+package com.persagy.framework.common.exception.enums;
+
+/**
+ * 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
+ *
+ * 一共 10 位,分成四段
+ *
+ * 第一段,1 位,类型
+ *      1 - 业务级别异常
+ *      x - 预留
+ * 第二段,3 位,系统类型
+ *      001 - 用户系统
+ *      002 - 商品系统
+ *      003 - 订单系统
+ *      004 - 支付系统
+ *      005 - 优惠劵系统
+ *      ... - ...
+ * 第三段,3 位,模块
+ *      不限制规则。
+ *      一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子:
+ *          001 - OAuth2 模块
+ *          002 - User 模块
+ *          003 - MobileCode 模块
+ * 第四段,3 位,错误码
+ *       不限制规则。
+ *       一般建议,每个模块自增。
+ *
+ * @author  
+ */
+public class ServiceErrorCodeRange {
+
+    // 模块 system 错误码区间 [1-000-001-000 ~ 1-000-002-000]
+
+}

+ 122 - 0
iot-common/src/main/java/com.persagy/framework/common/exception/util/ServiceExceptionUtil.java

@@ -0,0 +1,122 @@
+package com.persagy.framework.common.exception.util;
+
+import com.persagy.framework.common.exception.ErrorCode;
+import com.persagy.framework.common.exception.ServiceException;
+import com.google.common.annotations.VisibleForTesting;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * {@link ServiceException} 工具类
+ *
+ * 目的在于,格式化异常信息提示。
+ * 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
+ *
+ * 因为 {@link #MESSAGES} 里面默认是没有异常信息提示的模板的,所以需要使用方自己初始化进去。目前想到的有几种方式:
+ *
+ * 1. 异常提示信息,写在枚举类中,例如说,cn.iocoder.oceans.user.api.constants.ErrorCodeEnum 类 + ServiceExceptionConfiguration
+ * 2. 异常提示信息,写在 .properties 等等配置文件
+ * 3. 异常提示信息,写在 Apollo 等等配置中心中,从而实现可动态刷新
+ * 4. 异常提示信息,存储在 db 等等数据库中,从而实现可动态刷新
+ */
+@Slf4j
+public class ServiceExceptionUtil {
+
+    /**
+     * 错误码提示模板
+     */
+    private static final ConcurrentMap<Integer, String> MESSAGES = new ConcurrentHashMap<>();
+
+    public static void putAll(Map<Integer, String> messages) {
+        ServiceExceptionUtil.MESSAGES.putAll(messages);
+    }
+
+    public static void put(Integer code, String message) {
+        ServiceExceptionUtil.MESSAGES.put(code, message);
+    }
+
+    public static void delete(Integer code, String message) {
+        ServiceExceptionUtil.MESSAGES.remove(code, message);
+    }
+
+    // ========== 和 ServiceException 的集成 ==========
+
+    public static ServiceException exception(ErrorCode errorCode) {
+        String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
+        return exception0(errorCode.getCode(), messagePattern);
+    }
+
+    public static ServiceException exception(ErrorCode errorCode, Object... params) {
+        String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
+        return exception0(errorCode.getCode(), messagePattern, params);
+    }
+
+    /**
+     * 创建指定编号的 ServiceException 的异常
+     *
+     * @param code 编号
+     * @return 异常
+     */
+    public static ServiceException exception(Integer code) {
+        return exception0(code, MESSAGES.get(code));
+    }
+
+    /**
+     * 创建指定编号的 ServiceException 的异常
+     *
+     * @param code 编号
+     * @param params 消息提示的占位符对应的参数
+     * @return 异常
+     */
+    public static ServiceException exception(Integer code, Object... params) {
+        return exception0(code, MESSAGES.get(code), params);
+    }
+
+    public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
+        String message = doFormat(code, messagePattern, params);
+        return new ServiceException(code, message);
+    }
+
+    // ========== 格式化方法 ==========
+
+    /**
+     * 将错误编号对应的消息使用 params 进行格式化。
+     *
+     * @param code           错误编号
+     * @param messagePattern 消息模版
+     * @param params         参数
+     * @return 格式化后的提示
+     */
+    @VisibleForTesting
+    public static String doFormat(int code, String messagePattern, Object... params) {
+        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
+        int i = 0;
+        int j;
+        int l;
+        for (l = 0; l < params.length; l++) {
+            j = messagePattern.indexOf("{}", i);
+            if (j == -1) {
+                log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
+                if (i == 0) {
+                    return messagePattern;
+                } else {
+                    sbuf.append(messagePattern.substring(i));
+                    return sbuf.toString();
+                }
+            } else {
+                sbuf.append(messagePattern, i, j);
+                sbuf.append(params[l]);
+                i = j + 2;
+            }
+        }
+        if (messagePattern.indexOf("{}", i) != -1) {
+            log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
+        }
+        sbuf.append(messagePattern.substring(i));
+        return sbuf.toString();
+    }
+
+}

+ 102 - 0
iot-common/src/main/java/com.persagy/framework/common/pojo/CommonResult.java

@@ -0,0 +1,102 @@
+package com.persagy.framework.common.pojo;
+
+import com.persagy.framework.common.exception.ErrorCode;
+import com.persagy.framework.common.exception.ServiceException;
+import com.persagy.framework.common.exception.enums.GlobalErrorCodeConstants;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import org.springframework.util.Assert;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * 通用返回
+ *
+ * @param <T> 数据泛型
+ */
+@Data
+public class CommonResult<T> implements Serializable {
+
+    /**
+     * 错误码
+     *
+     * @see ErrorCode#getCode()
+     */
+    private Integer code;
+    /**
+     * 返回数据
+     */
+    private T data;
+    /**
+     * 错误提示,用户可阅读
+     *
+     * @see ErrorCode#getMsg() ()
+     */
+    private String msg;
+
+    /**
+     * 将传入的 result 对象,转换成另外一个泛型结果的对象
+     *
+     * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
+     *
+     * @param result 传入的 result 对象
+     * @param <T> 返回的泛型
+     * @return 新的 CommonResult 对象
+     */
+    public static <T> CommonResult<T> error(CommonResult<?> result) {
+        return error(result.getCode(), result.getMsg());
+    }
+
+    public static <T> CommonResult<T> error(Integer code, String message) {
+        Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!");
+        CommonResult<T> result = new CommonResult<>();
+        result.code = code;
+        result.msg = message;
+        return result;
+    }
+
+    public static <T> CommonResult<T> error(ErrorCode errorCode) {
+        return error(errorCode.getCode(), errorCode.getMsg());
+    }
+
+    public static <T> CommonResult<T> success(T data) {
+        CommonResult<T> result = new CommonResult<>();
+        result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
+        result.data = data;
+        result.msg = "";
+        return result;
+    }
+
+    public static boolean isSuccess(Integer code) {
+        return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
+    }
+
+    @JsonIgnore // 避免 jackson 序列化
+    public boolean isSuccess() {
+        return isSuccess(code);
+    }
+
+    @JsonIgnore // 避免 jackson 序列化
+    public boolean isError() {
+        return !isSuccess();
+    }
+
+    // ========= 和 Exception 异常体系集成 =========
+
+    /**
+     * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
+     */
+    public void checkError() throws ServiceException {
+        if (isSuccess()) {
+            return;
+        }
+        // 业务异常
+        throw new ServiceException(code, msg);
+    }
+
+    public static <T> CommonResult<T> error(ServiceException serviceException) {
+        return error(serviceException.getCode(), serviceException.getMessage());
+    }
+
+}

+ 30 - 0
iot-common/src/main/java/com.persagy/framework/common/pojo/PageParam.java

@@ -0,0 +1,30 @@
+package com.persagy.framework.common.pojo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@ApiModel("分页参数")
+@Data
+public class PageParam implements Serializable {
+
+    private static final Integer PAGE_NO = 1;
+    private static final Integer PAGE_SIZE = 10;
+
+    @ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1")
+    @NotNull(message = "页码不能为空")
+    @Min(value = 1, message = "页码最小值为 1")
+    private Integer pageNo = PAGE_NO;
+
+    @ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10")
+    @NotNull(message = "每页条数不能为空")
+    @Min(value = 1, message = "页码最小值为 1")
+    @Max(value = 100, message = "页码最大值为 100")
+    private Integer pageSize = PAGE_SIZE;
+
+}

+ 38 - 0
iot-common/src/main/java/com.persagy/framework/common/pojo/PageResult.java

@@ -0,0 +1,38 @@
+package com.persagy.framework.common.pojo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+@ApiModel("分页结果")
+@Data
+public final class PageResult<T> implements Serializable {
+
+    @ApiModelProperty(value = "数据", required = true)
+    private List<T> list;
+
+    @ApiModelProperty(value = "总量", required = true)
+    private Long total;
+
+    public PageResult() {
+    }
+
+    public PageResult(List<T> list, Long total) {
+        this.list = list;
+        this.total = total;
+    }
+
+    public PageResult(Long total) {
+        this.list = new ArrayList<>();
+        this.total = total;
+    }
+
+    public static <T> PageResult<T> empty() {
+        return new PageResult<>(0L);
+    }
+
+}

+ 56 - 0
iot-common/src/main/java/com.persagy/framework/common/pojo/SortingField.java

@@ -0,0 +1,56 @@
+package com.persagy.framework.common.pojo;
+
+import java.io.Serializable;
+
+/**
+ * 排序字段 DTO
+ *
+ * 类名加了 ing 的原因是,避免和 ES SortField 重名。
+ */
+public class SortingField implements Serializable {
+
+    /**
+     * 顺序 - 升序
+     */
+    public static final String ORDER_ASC = "asc";
+    /**
+     * 顺序 - 降序
+     */
+    public static final String ORDER_DESC = "desc";
+
+    /**
+     * 字段
+     */
+    private String field;
+    /**
+     * 顺序
+     */
+    private String order;
+
+    // 空构造方法,解决反序列化
+    public SortingField() {
+    }
+
+    public SortingField(String field, String order) {
+        this.field = field;
+        this.order = order;
+    }
+
+    public String getField() {
+        return field;
+    }
+
+    public SortingField setField(String field) {
+        this.field = field;
+        return this;
+    }
+
+    public String getOrder() {
+        return order;
+    }
+
+    public SortingField setOrder(String order) {
+        this.order = order;
+        return this;
+    }
+}

+ 0 - 0
iot-common/src/main/java/com.persagy/framework/common/test/WebFilterOrderEnum.java


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