diff --git a/demo-server/Dockerfile b/demo-server/Dockerfile
new file mode 100644
index 00000000..10275d6f
--- /dev/null
+++ b/demo-server/Dockerfile
@@ -0,0 +1,13 @@
+FROM openjdk:17-jre-slim
+
+# 设置应用目录
+WORKDIR /app
+
+# 复制应用文件
+COPY target/demo-server.jar /app/demo-server.jar
+
+# 暴露端口
+EXPOSE 48100
+
+# 运行应用
+ENTRYPOINT ["java", "-jar", "/app/demo-server.jar"]
diff --git a/demo-server/pom.xml b/demo-server/pom.xml
new file mode 100644
index 00000000..c9a48511
--- /dev/null
+++ b/demo-server/pom.xml
@@ -0,0 +1,99 @@
+
+
+
+ cn.iocoder.cloud
+ yudao
+ ${revision}
+
+ 4.0.0
+ demo-server
+ jar
+
+ demo-server
+ Demo 服务器
+
+
+
+ cn.iocoder.cloud
+ yudao-module-system-server
+ ${revision}
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+
+ cn.iocoder.cloud
+ yudao-module-infra-server
+ ${revision}
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-protection
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-rpc
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+
+
+
+
+
+ ${project.artifactId}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+ repackage
+
+
+
+
+
+
+
+
diff --git a/demo-server/src/main/java/cn/iocoder/yudao/demoserver/DemoServerApplication.java b/demo-server/src/main/java/cn/iocoder/yudao/demoserver/DemoServerApplication.java
new file mode 100644
index 00000000..f371b001
--- /dev/null
+++ b/demo-server/src/main/java/cn/iocoder/yudao/demoserver/DemoServerApplication.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.demoserver;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Demo 服务器的启动类
+ *
+ * @author chenbw
+ */
+@SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${yudao.info.base-package}
+@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}.demoserver", "${yudao.info.base-package}.module"},
+ excludeName = {})
+public class DemoServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DemoServerApplication.class, args);
+ }
+
+}
diff --git a/demo-server/src/main/java/cn/iocoder/yudao/demoserver/controller/demo/DemoController.java b/demo-server/src/main/java/cn/iocoder/yudao/demoserver/controller/demo/DemoController.java
new file mode 100644
index 00000000..6cc74aa2
--- /dev/null
+++ b/demo-server/src/main/java/cn/iocoder/yudao/demoserver/controller/demo/DemoController.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.demoserver.controller.demo;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * demo 控制器
+ *
+ * @author chenbw
+ */
+@Tag(name = "demo")
+@RestController
+@RequestMapping("/demo")
+public class DemoController {
+
+ @GetMapping("/hello")
+ @Operation(summary = "Hello demo")
+ public CommonResult hello() {
+ return success("Hello, demo!");
+ }
+
+}
diff --git a/demo-server/src/main/resources/application-dev.yml b/demo-server/src/main/resources/application-dev.yml
new file mode 100644
index 00000000..68d3e40e
--- /dev/null
+++ b/demo-server/src/main/resources/application-dev.yml
@@ -0,0 +1,118 @@
+server:
+ servlet:
+ encoding:
+ enabled: true
+ charset: UTF-8 # 必须设置 UTF-8,避免 WebFlux 流式返回(AI 场景)会乱码问题
+ force: true
+
+---
+spring:
+ # 数据源配置项
+ 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 # 配置检测连接是否有效
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+ # 设置默认的数据源或者数据源组,默认 master
+ primary: master
+ datasource:
+ # 主库
+ master:
+ url: jdbc:mysql://127.0.0.1:3306/${dbName}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
+ username: root
+ password:
+ # 从库
+ slave:
+ lazy: true # 开启懒加载,保证启动速度
+ url: jdbc:mysql://127.0.0.1:3306/${dbName}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
+ username: root
+ password:
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ data:
+ redis:
+ host: 127.0.0.1 # 地址
+ port: 6379 # 端口
+ database: 1 # 数据库索引
+# password: # 密码,建议生产环境开启
+
+---
+xxl:
+ job:
+ enabled: false # 是否开启调度中心,默认为 true 开启
+ admin:
+ addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
+
+---
+# rocketmq 配置项,对应 RocketMQProperties 配置类
+rocketmq:
+ name-server: 127.0.0.1:9876 # RocketMQ Namesrv
+
+spring:
+ # RabbitMQ 配置项,对应 RabbitProperties 配置类
+ rabbitmq:
+ host: 127.0.0.1 # RabbitMQ 服务的地址
+ port: 5672 # RabbitMQ 服务的端口
+ username: guest # RabbitMQ 服务的账号
+ password: guest # RabbitMQ 服务的密码
+ # Kafka 配置项,对应 KafkaProperties 配置类
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
+
+---
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+---
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# 日志文件配置
+logging:
+ file:
+ name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
+ level:
+ # 配置自己写的 MyBatis Mapper 打印日志
+ cn.iocoder.yudao.demoserver.dal.mysql: debug
+
+---
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ demo: false # 开启演示模式
+ # 附件加密相关配置
+ AES:
+ key: "0123456789abcdef0123456789abcdef"
diff --git a/demo-server/src/main/resources/application-local.yml b/demo-server/src/main/resources/application-local.yml
new file mode 100644
index 00000000..23924880
--- /dev/null
+++ b/demo-server/src/main/resources/application-local.yml
@@ -0,0 +1,117 @@
+server:
+ servlet:
+ encoding:
+ enabled: true
+ charset: UTF-8 # 必须设置 UTF-8,避免 WebFlux 流式返回(AI 场景)会乱码问题
+ force: true
+
+---
+spring:
+ # 数据源配置项
+ 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: 1 # 初始连接数
+ min-idle: 1 # 最小连接池数量
+ 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
+ # 设置默认的数据源或者数据源组,默认 master
+ primary: master
+ datasource:
+ master:
+ url: jdbc:dm://localhost:5236?schema=SYSDBA
+ username: SYSDBA
+ password: P@ssword25
+ slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
+ lazy: true # 开启懒加载,保证启动速度
+ url: jdbc:dm://localhost:5236?schema=SYSDBA
+ username: SYSDBA
+ password: P@ssword25
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ data:
+ redis:
+ host: localhost # 地址
+ port: 6379 # 端口
+ database: 1 # 数据库索引
+# password: # 密码,建议生产环境开启
+
+---
+xxl:
+ job:
+ enabled: false # 是否开启调度中心,默认为 true 开启
+ admin:
+ addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
+
+---
+# rocketmq 配置项,对应 RocketMQProperties 配置类
+rocketmq:
+ name-server: localhost:9876 # RocketMQ Namesrv
+
+spring:
+ # RabbitMQ 配置项,对应 RabbitProperties 配置类
+ rabbitmq:
+ host: 127.0.0.1 # RabbitMQ 服务的地址
+ port: 5672 # RabbitMQ 服务的端口
+ username: rabbit # RabbitMQ 服务的账号
+ password: rabbit # RabbitMQ 服务的密码
+ # Kafka 配置项,对应 KafkaProperties 配置类
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
+
+---
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+---
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# 日志文件配置
+logging:
+ file:
+ name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
+ level:
+ # 配置自己写的 MyBatis Mapper 打印日志
+ cn.iocoder.yudao.demoserver.dal.mysql: debug
+ root: info
+
+---
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ demo: false # 开启演示模式
+ # 附件加密相关配置
+ AES:
+ key: "0123456789abcdef0123456789abcdef"
diff --git a/demo-server/src/main/resources/application.yml b/demo-server/src/main/resources/application.yml
new file mode 100644
index 00000000..b4b802eb
--- /dev/null
+++ b/demo-server/src/main/resources/application.yml
@@ -0,0 +1,109 @@
+server:
+ port: 48100
+
+spring:
+ application:
+ name: demo-server
+ main:
+ allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
+ 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
+
+ # Cache 配置项
+ cache:
+ type: REDIS
+ redis:
+ time-to-live: 1h # 设置过期时间为 1 小时
+
+---
+springdoc:
+ api-docs:
+ enabled: true
+ path: /v3/api-docs
+ swagger-ui:
+ enabled: true
+ path: /swagger-ui
+ default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
+
+knife4j:
+ enable: true
+ setting:
+ language: zh_cn
+
+# MyBatis Plus 的配置项
+mybatis-plus:
+ configuration:
+ map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
+ global-config:
+ db-config:
+ id-type: NONE # "智能"模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
+ logic-delete-value: 1 # 逻辑已删除值(默认为 1)
+ logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+ banner: false # 关闭控制台的 Banner 打印
+ type-aliases-package: cn.iocoder.yudao.demoserver.dal.dataobject
+
+# Spring Data Redis 配置
+spring:
+ data:
+ redis:
+ repositories:
+ enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
+
+---
+yudao:
+ info:
+ version: 1.0.0
+ base-package: cn.iocoder.yudao
+ author: chenbw
+ description: Demo 服务器
+ web:
+ admin-ui:
+ url: http://localhost:3000 # Admin 管理后台 UI 的地址
+ xss:
+ enable: false
+ security:
+ permit-all_urls: []
+ websocket:
+ enable: true # websocket的开关
+ path: /infra/ws # 路径
+ sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq
+ sender-rocketmq:
+ topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic
+ consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group
+ sender-rabbitmq:
+ exchange: ${spring.application.name}-websocket-exchange # 消息发送的 RabbitMQ Exchange
+ queue: ${spring.application.name}-websocket-queue # 消息发送的 RabbitMQ Queue
+ sender-kafka:
+ topic: ${spring.application.name}-websocket # 消息发送的 Kafka Topic
+ consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 Kafka Consumer Group
+ swagger:
+ title: demo-server
+ description: Demo 服务器
+ version: ${yudao.info.version}
+ email: xingyu4j@vip.qq.com
+ license: MIT
+ license-url: https://gitee.com/zhijiantianya/ruoyi-vue-pro/blob/master/LICENSE
+ codegen:
+ base-package: cn.iocoder.yudao.demoserver
+ db-schemas: ${spring.datasource.dynamic.datasource.master.name}
+ front-type: 20 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类
+ vo-type: 10 # VO 的类型,参见 CodegenVOTypeEnum 枚举类
+ delete-batch-enable: true # 是否生成批量删除接口
+ unit-test-enable: false # 是否生成单元测试
+
+debug: false
diff --git a/yudao-framework/yudao-spring-boot-starter-test/pom.xml b/yudao-framework/yudao-spring-boot-starter-test/pom.xml
index 05e5c681..d12e94d3 100644
--- a/yudao-framework/yudao-spring-boot-starter-test/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-test/pom.xml
@@ -56,5 +56,10 @@
uk.co.jemos.podam
podam
+
+
+ org.apache.velocity
+ velocity-engine-core
+
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/GeneratorUtils.java b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/GeneratorUtils.java
new file mode 100644
index 00000000..9195f2cc
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/GeneratorUtils.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.framework.test.core.ut;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+/**
+ * 代码生成器通用工具类
+ *
+ * @author ZT
+ */
+public class GeneratorUtils {
+
+ /**
+ * 首字母大写
+ *
+ * @param str 字符串
+ * @return 首字母大写后的字符串
+ */
+ public static String capitalize(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+ return str.substring(0, 1).toUpperCase() + str.substring(1);
+ }
+
+ /**
+ * 将小驼峰命名转换为短横线分割的命名
+ * 例如:orderManagement -> order-management
+ *
+ * @param camelCase 驼峰命名的字符串
+ * @return 短横线分割的字符串
+ */
+ public static String camelToKebabCase(String camelCase) {
+ if (camelCase == null || camelCase.isEmpty()) {
+ return camelCase;
+ }
+
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < camelCase.length(); i++) {
+ char c = camelCase.charAt(i);
+ if (Character.isUpperCase(c) && i > 0) {
+ result.append('-');
+ }
+ result.append(Character.toLowerCase(c));
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * 将 kebab-case 转换为 PascalCase
+ * 例如:demo-server -> DemoServer
+ *
+ * @param kebabCase 短横线分割的字符串
+ * @return 帕斯卡命名的字符串
+ */
+ public static String toPascalCase(String kebabCase) {
+ if (kebabCase == null || kebabCase.isEmpty()) {
+ return "";
+ }
+ return Arrays.stream(kebabCase.split("-"))
+ .map(s -> {
+ if (s.isEmpty()) {
+ return "";
+ }
+ return s.substring(0, 1).toUpperCase() + s.substring(1);
+ })
+ .collect(Collectors.joining());
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ModuleGenerator.java b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ModuleGenerator.java
index e6022b3c..7935f102 100644
--- a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ModuleGenerator.java
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ModuleGenerator.java
@@ -4,11 +4,15 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Scanner;
-import java.util.stream.Stream;
+
+import static cn.iocoder.yudao.framework.test.core.ut.GeneratorUtils.camelToKebabCase;
+import static cn.iocoder.yudao.framework.test.core.ut.GeneratorUtils.capitalize;
/**
- * Yudao 模块代码生成器
+ * Yudao 模块代码生成器 - 使用 VM 模板
*
* 使用方法:
* 1. 在 IDE 中,右键运行 {@link #main(String[])} 方法
@@ -19,8 +23,7 @@ import java.util.stream.Stream;
*/
public class ModuleGenerator {
- private static final String TEMPLATE_PATH = "yudao-module-template";
- private static final String BASE_PACKAGE = "cn.iocoder.yudao.module.";
+ private static final TemplateEngine templateEngine = new TemplateEngine();
public static void main(String[] args) throws IOException {
// 1. 获取用户输入
@@ -38,12 +41,6 @@ public class ModuleGenerator {
// 2. 定义项目根路径
Path projectRoot = Paths.get("").toAbsolutePath();
- Path templateDir = projectRoot.resolve(TEMPLATE_PATH);
-
- if (!Files.exists(templateDir)) {
- System.err.println("错误:模板目录不存在: " + templateDir);
- return;
- }
// 3. 批量创建模块
for (int i = 0; i < modules.length; i++) {
@@ -56,7 +53,7 @@ public class ModuleGenerator {
int modulePort = startPort + i;
System.out.println("\n=== 开始创建模块: " + moduleName + " (端口: " + modulePort + ") ===");
- createSingleModule(templateDir, projectRoot, moduleName, author, modulePort);
+ createSingleModule(projectRoot, moduleName, author, modulePort);
}
System.out.println("\n所有模块创建完成!");
@@ -70,12 +67,13 @@ public class ModuleGenerator {
}
}
- private static void createSingleModule(Path templateDir, Path projectRoot, String moduleName, String author, int port) throws IOException {
- String packageName = moduleName.replace("-", "");
- String capitalizedModuleName = capitalize(packageName);
-
+ private static void createSingleModule(Path projectRoot, String moduleName, String author, int port) throws IOException {
// 将小驼峰转换为短横线分割的模块名
String dashModuleName = camelToKebabCase(moduleName);
+
+ // packageName 应该是去掉短横线的版本,用于 Java 包名
+ String packageName = dashModuleName.replace("-", "");
+ String capitalizedModuleName = capitalize(packageName);
// 定义新模块路径
Path newModuleDir = projectRoot.resolve("yudao-module-" + dashModuleName);
@@ -87,214 +85,98 @@ public class ModuleGenerator {
System.out.println("将在以下位置创建新模块: " + newModuleDir);
- // 复制并处理文件
- copyAndProcessDirectory(templateDir, newModuleDir, dashModuleName, packageName, capitalizedModuleName, author, port);
+ // 准备模板变量
+ Map variables = new HashMap<>();
+ variables.put("moduleName", moduleName);
+ variables.put("dashModuleName", dashModuleName);
+ variables.put("packageName", packageName);
+ variables.put("capitalizedModuleName", capitalizedModuleName);
+ variables.put("baseName", dashModuleName.toLowerCase()); // 使用 dashModuleName 的小写版本
+ variables.put("applicationClassName", capitalizedModuleName + "ServerApplication");
+ variables.put("basePackage", "cn.iocoder.yudao");
+ variables.put("author", author);
+ variables.put("port", port);
+ variables.put("moduleDescription", capitalizedModuleName + " 模块");
- // 创建启动类
- createApplicationClass(newModuleDir, dashModuleName, packageName, capitalizedModuleName, author);
-
- // 创建配置文件
- createApplicationYml(newModuleDir, dashModuleName, port, author);
+ // 创建模块结构
+ createModuleStructure(newModuleDir, variables);
System.out.println("模块 '" + dashModuleName + "' 创建成功!");
}
- private static void copyAndProcessDirectory(Path sourceDir, Path targetDir, String moduleName, String packageName, String capitalizedModuleName, String author, int port) throws IOException {
- try (Stream stream = Files.walk(sourceDir)) {
- stream.filter(path -> shouldIncludePath(sourceDir.relativize(path)))
- .forEach(sourcePath -> {
- try {
- Path relativePath = sourceDir.relativize(sourcePath);
- Path targetPath = targetDir.resolve(relativePath);
- String targetPathStr = targetPath.toString();
+ private static void createModuleStructure(Path moduleDir, Map variables) throws IOException {
+ String dashModuleName = (String) variables.get("dashModuleName");
+ String packageName = (String) variables.get("packageName");
+ String basePackage = (String) variables.get("basePackage");
+ String capitalizedModuleName = (String) variables.get("capitalizedModuleName");
+ String applicationClassName = (String) variables.get("applicationClassName");
+ String baseName = (String) variables.get("baseName");
- // 替换路径中的 'template'
- targetPathStr = targetPathStr.replace("template", moduleName);
- // 替换包名路径
- targetPathStr = targetPathStr.replace(java.io.File.separator + "template" + java.io.File.separator, java.io.File.separator + packageName + java.io.File.separator);
- targetPath = Paths.get(targetPathStr);
+ // 创建主模块目录
+ Files.createDirectories(moduleDir);
- if (Files.isDirectory(sourcePath)) {
- Files.createDirectories(targetPath);
- } else {
- // 确保父目录存在
- Files.createDirectories(targetPath.getParent());
- String content = new String(Files.readAllBytes(sourcePath));
- String newContent = processFileContent(content, sourcePath.getFileName().toString(), moduleName, packageName, capitalizedModuleName, author, port);
- Files.write(targetPath, newContent.getBytes());
- }
- } catch (IOException e) {
- throw new RuntimeException("处理文件失败: " + sourcePath, e);
- }
- });
- }
- }
+ // 1. 创建主模块 pom.xml
+ templateEngine.renderToFile("generator/module/pom.xml.vm",
+ moduleDir.resolve("pom.xml"), variables);
- private static boolean shouldIncludePath(Path relativePath) {
- String pathStr = relativePath.toString();
+ // 2. 创建 API 模块
+ Path apiDir = moduleDir.resolve("yudao-module-" + dashModuleName + "-api");
+ Files.createDirectories(apiDir);
- // 排除 target 目录
- if (pathStr.contains("target")) {
- return false;
- }
+ // API 模块的 Java 源码目录
+ Path apiJavaDir = apiDir.resolve("src/main/java")
+ .resolve(basePackage.replace(".", "/"))
+ .resolve("module")
+ .resolve(packageName);
+ Files.createDirectories(apiJavaDir);
- // 排除 .flattened-pom.xml 文件
- if (pathStr.contains(".flattened-pom.xml")) {
- return false;
- }
+ // API 模块的错误码目录
+ Path enumsDir = apiJavaDir.resolve("enums");
+ Files.createDirectories(enumsDir);
- // 如果是根目录或者包含 example 的路径,则包含
- if (relativePath.getNameCount() <= 3) { // 根目录和基础包结构
- return true;
- }
-
- // 只包含 example 相关的文件和目录
- return pathStr.contains("example") || pathStr.contains("ErrorCodeConstants");
- }
+ templateEngine.renderToFile("generator/module/api-pom.xml.vm",
+ apiDir.resolve("pom.xml"), variables);
+ templateEngine.renderToFile("generator/module/ErrorCodeConstants.java.vm",
+ enumsDir.resolve("ErrorCodeConstants.java"), variables);
- private static String processFileContent(String content, String fileName, String moduleName, String packageName, String capitalizedModuleName, String author, int port) {
- // 如果是 ErrorCodeConstants 文件,特殊处理
- if (fileName.equals("ErrorCodeConstants.java")) {
- return processErrorCodeConstants(content, moduleName, packageName, author);
- }
+ // 3. 创建 Server 模块
+ Path serverDir = moduleDir.resolve("yudao-module-" + dashModuleName + "-server");
+ Files.createDirectories(serverDir);
- // 处理配置文件
- if (fileName.endsWith(".yml") || fileName.endsWith(".yaml")) {
- String newContent = content;
-
- // 替换应用名称
- newContent = newContent.replace("name: yudao-module-template-server", "name: yudao-module-" + moduleName + "-server");
-
- // 处理端口配置
- if (newContent.contains("port:")) {
- // 使用更精确的正则表达式来匹配端口行
- newContent = newContent.replaceAll("(?m)^(\\s*)port:\\s*\\d+\\s*$", "$1port: " + port);
- } else if (newContent.contains("server:")) {
- // 如果有server配置但没有port,在server下添加port
- newContent = newContent.replaceAll("(?m)^(\\s*)server:\\s*$", "$1server:\n$1 port: " + port);
- } else {
- // 如果完全没有server配置,在文件开头添加
- newContent = "server:\n port: " + port + "\n\n" + newContent;
- }
-
- return newContent;
- }
+ // Server 模块的 Java 源码目录
+ Path serverJavaDir = serverDir.resolve("src/main/java")
+ .resolve(basePackage.replace(".", "/"))
+ .resolve("module")
+ .resolve(packageName);
+ Files.createDirectories(serverJavaDir);
- return content.replace("yudao-module-template", "yudao-module-" + moduleName)
- .replace("cn.iocoder.yudao.module.template", BASE_PACKAGE + packageName)
- .replace("TemplateServerApplication", capitalizedModuleName + "ServerApplication")
- .replace("样例模块", moduleName + "模块")
- .replace("周迪", author) // 替换作者
- .replace("template", moduleName) // 最后替换纯 "template"
- .replace("Example", capitalize(moduleName) + "Example");
- }
-
- private static String processErrorCodeConstants(String content, String moduleName, String packageName, String author) {
- // 构建新的 ErrorCodeConstants 内容,只保留基本结构,移除具体的错误码常量
- StringBuilder sb = new StringBuilder();
- sb.append("package ").append(BASE_PACKAGE).append(packageName).append(".enums;\n\n");
- sb.append("import cn.iocoder.yudao.framework.common.exception.ErrorCode;\n\n");
- sb.append("/**\n");
- sb.append(" * ").append(moduleName).append(" 错误码枚举类\n");
- sb.append(" *\n");
- sb.append(" * ").append(moduleName).append(" 系统,使用 1-xxx-xxx-xxx 段\n");
- sb.append(" *\n");
- sb.append(" * @author ").append(author).append("\n");
- sb.append(" */\n");
- sb.append("public interface ErrorCodeConstants {\n\n");
- sb.append(" // ========== 示例模块 1-001-000-000 ==========\n");
- sb.append(" // ErrorCode EXAMPLE_NOT_EXISTS = new ErrorCode(1_001_000_001, \"示例不存在\");\n\n");
- sb.append("}\n");
+ // Controller 目录
+ Path controllerDir = serverJavaDir.resolve("controller/admin").resolve(baseName);
+ Files.createDirectories(controllerDir);
- return sb.toString();
- }
-
- /**
- * 创建启动类
- */
- private static void createApplicationClass(Path moduleDir, String dashModuleName, String packageName, String capitalizedModuleName, String author) throws IOException {
- // 创建启动类目录
- Path applicationDir = moduleDir.resolve("yudao-module-" + dashModuleName + "-server")
- .resolve("src/main/java/cn/iocoder/yudao/module/" + packageName);
- Files.createDirectories(applicationDir);
-
- // 创建启动类文件
- Path applicationFile = applicationDir.resolve(capitalizedModuleName + "ServerApplication.java");
+ // Security 配置目录
+ Path securityConfigDir = serverJavaDir.resolve("framework/security/config");
+ Files.createDirectories(securityConfigDir);
- StringBuilder sb = new StringBuilder();
- sb.append("package ").append(BASE_PACKAGE).append(packageName).append(";\n\n");
- sb.append("import org.springframework.boot.SpringApplication;\n");
- sb.append("import org.springframework.boot.autoconfigure.SpringBootApplication;\n\n");
- sb.append("/**\n");
- sb.append(" * ").append(dashModuleName).append(" 模块的启动类\n");
- sb.append(" *\n");
- sb.append(" * @author ").append(author).append("\n");
- sb.append(" */\n");
- sb.append("@SpringBootApplication\n");
- sb.append("public class ").append(capitalizedModuleName).append("ServerApplication {\n\n");
- sb.append(" public static void main(String[] args) {\n");
- sb.append(" SpringApplication.run(").append(capitalizedModuleName).append("ServerApplication.class, args);\n");
- sb.append(" }\n\n");
- sb.append("}\n");
-
- Files.write(applicationFile, sb.toString().getBytes());
- System.out.println("创建启动类: " + applicationFile);
- }
-
- /**
- * 创建配置文件 application.yml
- */
- private static void createApplicationYml(Path moduleDir, String dashModuleName, int port, String author) throws IOException {
- // 创建配置文件目录
- Path resourcesDir = moduleDir.resolve("yudao-module-" + dashModuleName + "-server")
- .resolve("src/main/resources");
+ // 资源目录
+ Path resourcesDir = serverDir.resolve("src/main/resources");
Files.createDirectories(resourcesDir);
- // 创建配置文件
- Path configFile = resourcesDir.resolve("application.yml");
-
- StringBuilder sb = new StringBuilder();
- sb.append("server:\n");
- sb.append(" port: ").append(port).append("\n\n");
- sb.append("spring:\n");
- sb.append(" application:\n");
- sb.append(" name: yudao-module-").append(dashModuleName).append("-server\n\n");
- sb.append("# 模块配置\n");
- sb.append("yudao:\n");
- sb.append(" info:\n");
- sb.append(" version: 1.0.0\n");
- sb.append(" base-package: cn.iocoder.yudao.module.").append(dashModuleName.replace("-", "")).append("\n");
- sb.append(" author: ").append(author).append("\n");
+ templateEngine.renderToFile("generator/module/server-pom.xml.vm",
+ serverDir.resolve("pom.xml"), variables);
+ templateEngine.renderToFile("generator/module/ServerApplication.java.vm",
+ serverJavaDir.resolve(applicationClassName + ".java"), variables);
+ templateEngine.renderToFile("generator/module/Controller.java.vm",
+ controllerDir.resolve(capitalizedModuleName + "Controller.java"), variables);
+ templateEngine.renderToFile("generator/module/SecurityConfiguration.java.vm",
+ securityConfigDir.resolve("SecurityConfiguration.java"), variables);
+ templateEngine.renderToFile("generator/module/application.yml.vm",
+ resourcesDir.resolve("application.yml"), variables);
+ templateEngine.renderToFile("generator/module/application-dev.yml.vm",
+ resourcesDir.resolve("application-dev.yml"), variables);
+ templateEngine.renderToFile("generator/module/application-local.yml.vm",
+ resourcesDir.resolve("application-local.yml"), variables);
- Files.write(configFile, sb.toString().getBytes());
- System.out.println("创建配置文件: " + configFile);
}
- private static String capitalize(String str) {
- if (str == null || str.isEmpty()) {
- return str;
- }
- return str.substring(0, 1).toUpperCase() + str.substring(1);
- }
-
- /**
- * 将小驼峰命名转换为短横线分割的命名
- * 例如:orderManagement -> order-management
- */
- private static String camelToKebabCase(String camelCase) {
- if (camelCase == null || camelCase.isEmpty()) {
- return camelCase;
- }
-
- StringBuilder result = new StringBuilder();
- for (int i = 0; i < camelCase.length(); i++) {
- char c = camelCase.charAt(i);
- if (Character.isUpperCase(c) && i > 0) {
- result.append('-');
- }
- result.append(Character.toLowerCase(c));
- }
-
- return result.toString();
- }
}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ServerGenerator.java b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ServerGenerator.java
index 498e8055..5e5f8384 100644
--- a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ServerGenerator.java
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ServerGenerator.java
@@ -4,13 +4,15 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Scanner;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+
+import static cn.iocoder.yudao.framework.test.core.ut.GeneratorUtils.capitalize;
+import static cn.iocoder.yudao.framework.test.core.ut.GeneratorUtils.toPascalCase;
/**
- * Yudao Server 代码生成器
+ * Yudao Server 代码生成器 - 使用 VM 模板
*
* 使用方法:
* 1. 在 IDE 中,右键运行 {@link #main(String[])} 方法
@@ -21,8 +23,8 @@ import java.util.stream.Stream;
*/
public class ServerGenerator {
- private static final String TEMPLATE_PATH = "yudao-server";
private static final String BASE_PACKAGE_PREFIX = "cn.iocoder.yudao.";
+ private static final TemplateEngine templateEngine = new TemplateEngine();
public static void main(String[] args) throws IOException {
// 1. 获取用户输入
@@ -39,15 +41,7 @@ public class ServerGenerator {
String[] servers = baseNames.split(",");
// 2. 定义项目根路径
- // 注意:请在项目的根目录(例如 ztcloud)下运行该生成器
Path projectRoot = Paths.get("").toAbsolutePath();
- Path templateDir = projectRoot.resolve(TEMPLATE_PATH);
-
- if (!Files.exists(templateDir)) {
- System.err.println("错误:模板目录 '" + templateDir + "' 不存在。");
- System.err.println("请确保在项目根目录下运行此生成器。");
- return;
- }
// 3. 批量创建服务器
for (int i = 0; i < servers.length; i++) {
@@ -61,7 +55,7 @@ public class ServerGenerator {
int serverPort = startPort + i;
System.out.println("\n=== 开始创建 Server: " + serverName + " (端口: " + serverPort + ") ===");
- createSingleServer(templateDir, projectRoot, serverName, author, serverPort);
+ createSingleServer(projectRoot, serverName, baseName, author, serverPort);
}
System.out.println("\n所有服务器创建完成!");
@@ -75,9 +69,10 @@ public class ServerGenerator {
}
}
- private static void createSingleServer(Path templateDir, Path projectRoot, String serverName, String author, int port) throws IOException {
+ private static void createSingleServer(Path projectRoot, String serverName, String baseName, String author, int port) throws IOException {
String packageName = serverName.replace("-", "");
String capitalizedName = toPascalCase(serverName);
+ String capitalizedBaseName = capitalize(baseName);
// 定义新 Server 路径
Path newServerDir = projectRoot.resolve(serverName);
@@ -89,108 +84,72 @@ public class ServerGenerator {
System.out.println("将在以下位置创建新 Server: " + newServerDir);
- // 复制并处理文件
- copyAndProcessDirectory(templateDir, newServerDir, serverName, packageName, capitalizedName, author, port);
+ // 准备模板变量
+ Map variables = new HashMap<>();
+ variables.put("serverName", serverName);
+ variables.put("baseName", baseName);
+ variables.put("capitalizedBaseName", capitalizedBaseName);
+ variables.put("packageName", packageName);
+ variables.put("applicationClassName", capitalizedName + "Application");
+ variables.put("basePackage", BASE_PACKAGE_PREFIX.substring(0, BASE_PACKAGE_PREFIX.length() - 1)); // 去掉末尾的点
+ variables.put("author", author);
+ variables.put("port", port);
+ variables.put("serverDescription", capitalizedBaseName + " 服务器");
+
+ // 创建目录结构和文件
+ createServerStructure(newServerDir, variables);
}
- private static void copyAndProcessDirectory(Path sourceDir, Path targetDir, String serverName, String packageName, String capitalizedName, String author, int port) throws IOException {
- try (Stream stream = Files.walk(sourceDir)) {
- stream.forEach(sourcePath -> {
- try {
- Path relativePath = sourceDir.relativize(sourcePath);
- if (!shouldIncludePath(relativePath)) {
- return;
- }
+ private static void createServerStructure(Path serverDir, Map variables) throws IOException {
+ String packageName = (String) variables.get("packageName");
+ String basePackage = (String) variables.get("basePackage");
+ String baseName = (String) variables.get("baseName");
+ String applicationClassName = (String) variables.get("applicationClassName");
- // 处理路径和文件名
- String javaPackagePath = "src" + java.io.File.separator + "main" + java.io.File.separator + "java" + java.io.File.separator
- + "cn" + java.io.File.separator + "iocoder" + java.io.File.separator + "yudao" + java.io.File.separator;
- String targetPathStr = relativePath.toString()
- .replace(javaPackagePath + "server", javaPackagePath + packageName)
- .replace("YudaoServerApplication.java", capitalizedName + "Application.java");
+ // 创建基础目录结构
+ Files.createDirectories(serverDir);
+
+ // 创建 Java 源码目录结构
+ Path javaSourceDir = serverDir.resolve("src/main/java")
+ .resolve(basePackage.replace(".", "/"))
+ .resolve(packageName);
+ Files.createDirectories(javaSourceDir);
+
+ // 创建控制器目录
+ Path controllerDir = javaSourceDir.resolve("controller").resolve(baseName);
+ Files.createDirectories(controllerDir);
+
+ // 创建资源目录
+ Path resourcesDir = serverDir.resolve("src/main/resources");
+ Files.createDirectories(resourcesDir);
- Path targetPath = targetDir.resolve(targetPathStr);
+ // 生成文件
+ // 1. pom.xml
+ templateEngine.renderToFile("generator/server/pom.xml.vm",
+ serverDir.resolve("pom.xml"), variables);
+
+ // 2. 启动类
+ templateEngine.renderToFile("generator/server/Application.java.vm",
+ javaSourceDir.resolve(applicationClassName + ".java"), variables);
+
+ // 3. 示例控制器
+ String controllerClassName = variables.get("capitalizedBaseName") + "Controller";
+ templateEngine.renderToFile("generator/server/Controller.java.vm",
+ controllerDir.resolve(controllerClassName + ".java"), variables);
+
+ // 4. 配置文件
+ templateEngine.renderToFile("generator/server/application.yml.vm",
+ resourcesDir.resolve("application.yml"), variables);
+ templateEngine.renderToFile("generator/server/application-dev.yml.vm",
+ resourcesDir.resolve("application-dev.yml"), variables);
+ templateEngine.renderToFile("generator/server/application-local.yml.vm",
+ resourcesDir.resolve("application-local.yml"), variables);
+
+ // 5. Dockerfile
+ templateEngine.renderToFile("generator/server/Dockerfile.vm",
+ serverDir.resolve("Dockerfile"), variables);
- if (Files.isDirectory(sourcePath)) {
- Files.createDirectories(targetPath);
- } else {
- Files.createDirectories(targetPath.getParent());
- String content = new String(Files.readAllBytes(sourcePath));
- String newContent = processFileContent(content, sourcePath.getFileName().toString(), serverName, packageName, capitalizedName, author, port);
- Files.write(targetPath, newContent.getBytes());
- }
- } catch (IOException e) {
- throw new RuntimeException("处理文件失败: " + sourcePath, e);
- }
- });
- }
+ System.out.println("Server '" + variables.get("serverName") + "' 创建成功!");
}
- private static boolean shouldIncludePath(Path relativePath) {
- String pathStr = relativePath.toString();
- // 排除 target, .idea, .git 等目录和 .iml, .flattened-pom.xml 等文件
- return !pathStr.startsWith("target")
- && !pathStr.startsWith(".idea")
- && !pathStr.startsWith(".git")
- && !pathStr.endsWith(".iml")
- && !pathStr.contains(".flattened-pom.xml");
- }
-
- private static String processFileContent(String content, String fileName, String serverName, String packageName, String capitalizedName, String author, int port) {
- String newContent = content.replace("芋道源码", author);
-
- switch (fileName) {
- case "pom.xml":
- // 替换 artifactId
- newContent = newContent.replace("yudao-server", "" + serverName + "");
- // 移除对 yudao-module-xxx-server 的依赖,但保留 system-server 和 infra-server
- return newContent.replaceAll("(?m)^\\s*\\s*cn\\.iocoder\\.cloud\\s*yudao-module-(?!system-server|infra-server).*-server[\\s\\S]*?\\s*", "");
- case "application.yaml":
- case "application-dev.yaml":
- case "application-prod.yaml":
- case "application-test.yaml":
- case "application-local.yaml":
- // 替换应用名称
- newContent = newContent.replace("name: yudao-server", "name: " + serverName);
-
- // 处理端口配置 - 更精确的替换逻辑
- if (newContent.contains("port:")) {
- // 使用更精确的正则表达式来匹配端口行
- newContent = newContent.replaceAll("(?m)^(\\s*)port:\\s*\\d+\\s*$", "$1port: " + port);
- } else if (newContent.contains("server:")) {
- // 如果有server配置但没有port,在server下添加port
- newContent = newContent.replaceAll("(?m)^(\\s*)server:\\s*$", "$1server:\n$1 port: " + port);
- } else {
- // 如果完全没有server配置,在文件开头添加
- newContent = "server:\n port: " + port + "\n\n" + newContent;
- }
- return newContent;
- case "YudaoServerApplication.java":
- return newContent.replace("package cn.iocoder.yudao.server;", "package " + BASE_PACKAGE_PREFIX + packageName + ";")
- .replace("YudaoServerApplication", capitalizedName + "Application");
- case "Dockerfile":
- return newContent.replace("yudao-server.jar", serverName + ".jar")
- .replace("/yudao-server", "/" + serverName);
- default:
- return newContent;
- }
- }
-
- /**
- * 将 kebab-case 转换为 PascalCase
- * 例如:demo-server -> DemoServer
- */
- private static String toPascalCase(String kebabCase) {
- if (kebabCase == null || kebabCase.isEmpty()) {
- return "";
- }
- return Arrays.stream(kebabCase.split("-"))
- .map(s -> {
- if (s.isEmpty()) {
- return "";
- }
- return s.substring(0, 1).toUpperCase() + s.substring(1);
- })
- .collect(Collectors.joining());
- }
}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/TemplateEngine.java b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/TemplateEngine.java
new file mode 100644
index 00000000..faefc36d
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/TemplateEngine.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.framework.test.core.ut;
+
+import cn.hutool.extra.template.TemplateConfig;
+import cn.hutool.extra.template.engine.velocity.VelocityEngine;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+/**
+ * 模板引擎工具类,用于代码生成
+ *
+ * @author ZT
+ */
+public class TemplateEngine {
+
+ private final cn.hutool.extra.template.TemplateEngine velocityEngine;
+
+ public TemplateEngine() {
+ TemplateConfig config = new TemplateConfig();
+ config.setResourceMode(TemplateConfig.ResourceMode.CLASSPATH);
+ this.velocityEngine = new VelocityEngine(config);
+ }
+
+ /**
+ * 渲染模板
+ *
+ * @param templatePath 模板路径(相对于 resources 目录)
+ * @param variables 变量映射
+ * @return 渲染后的内容
+ */
+ public String render(String templatePath, Map variables) {
+ return velocityEngine.getTemplate(templatePath).render(variables);
+ }
+
+ /**
+ * 渲染模板并写入文件
+ *
+ * @param templatePath 模板路径(相对于 resources 目录)
+ * @param outputPath 输出文件路径
+ * @param variables 变量映射
+ * @throws IOException IO异常
+ */
+ public void renderToFile(String templatePath, Path outputPath, Map variables) throws IOException {
+ String content = render(templatePath, variables);
+ Files.createDirectories(outputPath.getParent());
+ Files.write(outputPath, content.getBytes());
+ }
+
+ /**
+ * 检查模板文件是否存在
+ *
+ * @param templatePath 模板路径
+ * @return 是否存在
+ */
+ public boolean templateExists(String templatePath) {
+ try {
+ velocityEngine.getTemplate(templatePath);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/Controller.java.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/Controller.java.vm
new file mode 100644
index 00000000..5035c688
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/Controller.java.vm
@@ -0,0 +1,29 @@
+package ${basePackage}.module.${packageName}.controller.admin.${baseName};
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import ${basePackage}.framework.common.pojo.CommonResult;
+
+import static ${basePackage}.framework.common.pojo.CommonResult.success;
+
+/**
+ * ${capitalizedModuleName} 控制器
+ *
+ * @author ${author}
+ */
+@Tag(name = "管理后台 - ${capitalizedModuleName}")
+@RestController
+@RequestMapping("/admin/${dashModuleName}/${baseName}")
+public class ${capitalizedModuleName}Controller {
+
+ @GetMapping("/hello")
+ @Operation(summary = "Hello ${capitalizedModuleName}")
+ public CommonResult hello() {
+ return success("Hello, ${capitalizedModuleName}!");
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/ErrorCodeConstants.java.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/ErrorCodeConstants.java.vm
new file mode 100644
index 00000000..18f421f4
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/ErrorCodeConstants.java.vm
@@ -0,0 +1,17 @@
+package ${basePackage}.module.${packageName}.enums;
+
+import ${basePackage}.framework.common.exception.ErrorCode;
+
+/**
+ * ${dashModuleName} 错误码枚举类
+ *
+ * ${dashModuleName} 系统,使用 1-xxx-xxx-xxx 段
+ *
+ * @author ${author}
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 示例模块 1-001-000-000 ==========
+ ErrorCode EXAMPLE_NOT_EXISTS = new ErrorCode(1_001_000_001, "示例不存在");
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/SecurityConfiguration.java.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/SecurityConfiguration.java.vm
new file mode 100644
index 00000000..45b96570
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/SecurityConfiguration.java.vm
@@ -0,0 +1,42 @@
+package ${basePackage}.module.${packageName}.framework.security.config;
+
+import ${basePackage}.framework.security.config.AuthorizeRequestsCustomizer;
+import ${basePackage}.module.infra.enums.ApiConstants;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
+
+
+/**
+ * ${capitalizedModuleName} 模块的 Security 配置
+ *
+ * @author ${author}
+ */
+@Configuration(proxyBeanMethods = false)
+public class SecurityConfiguration {
+
+ @Bean
+ public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
+ return new AuthorizeRequestsCustomizer() {
+
+ @Override
+ public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) {
+ // Swagger 接口文档
+ registry.requestMatchers("/v3/api-docs/**").permitAll()
+ .requestMatchers("/webjars/**").permitAll()
+ .requestMatchers("/swagger-ui").permitAll()
+ .requestMatchers("/swagger-ui/**").permitAll();
+ // Druid 监控
+ registry.requestMatchers("/druid/**").permitAll();
+ // Spring Boot Actuator 的安全配置
+ registry.requestMatchers("/actuator").permitAll()
+ .requestMatchers("/actuator/**").permitAll();
+ // RPC 服务的安全配置
+ registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll();
+ }
+
+ };
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/ServerApplication.java.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/ServerApplication.java.vm
new file mode 100644
index 00000000..195a2fe4
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/ServerApplication.java.vm
@@ -0,0 +1,18 @@
+package ${basePackage}.module.${packageName};
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * ${moduleDescription}的启动类
+ *
+ * @author ${author}
+ */
+@SpringBootApplication
+public class ${applicationClassName} {
+
+ public static void main(String[] args) {
+ SpringApplication.run(${applicationClassName}.class, args);
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/api-pom.xml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/api-pom.xml.vm
new file mode 100644
index 00000000..70ac7841
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/api-pom.xml.vm
@@ -0,0 +1,46 @@
+
+
+
+ yudao-module-${dashModuleName}
+ cn.iocoder.cloud
+ ${revision}
+
+ 4.0.0
+ yudao-module-${dashModuleName}-api
+ jar
+
+ ${project.artifactId}
+
+ 暴露给其它模块调用
+
+
+
+
+ cn.iocoder.cloud
+ yudao-common
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-api
+ provided
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ true
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+ true
+
+
+
+
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application-dev.yml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application-dev.yml.vm
new file mode 100644
index 00000000..30abeecd
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application-dev.yml.vm
@@ -0,0 +1,107 @@
+spring:
+ # 数据源配置项
+ autoconfigure:
+ exclude:
+ 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:
+ url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO
+ username: SYSDBA
+ password: pgbsci6ddJ6Sqj@e
+ slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
+ lazy: true # 开启懒加载,保证启动速度
+ url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO
+ username: SYSDBA
+ password: pgbsci6ddJ6Sqj@e
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ data:
+ redis:
+ host: 172.16.46.63 # 地址
+ port: 30379 # 端口
+ database: 0 # 数据库索引
+# password: 123456 # 密码,建议生产环境开启
+
+xxl:
+ job:
+ admin:
+ addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# 日志文件配置
+logging:
+ file:
+ name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
+
+
+justauth:
+ enabled: true
+ type:
+ DINGTALK: # 钉钉
+ client-id: dingvrnreaje3yqvzhxg
+ client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
+ ignore-check-redirect-uri: true
+ WECHAT_ENTERPRISE: # 企业微信
+ client-id: wwd411c69a39ad2e54
+ client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw
+ agent-id: 1000004
+ ignore-check-redirect-uri: true
+ # noinspection SpringBootApplicationYaml
+ WECHAT_MINI_PROGRAM: # 微信小程序
+ client-id: ${dollar}{wx.miniapp.appid}
+ client-secret: ${dollar}{wx.miniapp.secret}
+ ignore-check-redirect-uri: true
+ ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验
+ WECHAT_MP: # 微信公众号
+ client-id: ${dollar}{wx.mp.app-id}
+ client-secret: ${dollar}{wx.mp.secret}
+ ignore-check-redirect-uri: true
+ cache:
+ type: REDIS
+ prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::
+ timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
+
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application-local.yml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application-local.yml.vm
new file mode 100644
index 00000000..3f8706d8
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application-local.yml.vm
@@ -0,0 +1,98 @@
+#set($dollar = '$')
+spring:
+ # 数据源配置项
+ autoconfigure:
+ # noinspection SpringBootApplicationYaml
+ 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: 1 # 初始连接数
+ min-idle: 1 # 最小连接池数量
+ 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:
+ url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO
+ username: SYSDBA
+ password: pgbsci6ddJ6Sqj@e
+ slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
+ lazy: true # 开启懒加载,保证启动速度
+ url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO
+ username: SYSDBA
+ password: pgbsci6ddJ6Sqj@e
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ data:
+ redis:
+ host: 172.16.46.63 # 地址
+ port: 30379 # 端口
+ database: 0 # 数据库索引
+# password: 123456 # 密码,建议生产环境开启
+
+xxl:
+ job:
+ admin:
+ addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# 日志文件配置
+logging:
+ level:
+ # 配置自己写的 MyBatis Mapper 打印日志
+ ${basePackage}.module.${packageName}.dal.mysql: debug
+ org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR
+
+mybatis-plus:
+ configuration:
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ env: # 多环境的配置项
+ tag: ${dollar}{HOSTNAME}
+ security:
+ mock-enable: true
+ access-log: # 访问日志的配置项
+ enable: true
+
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application.yml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application.yml.vm
new file mode 100644
index 00000000..0f5deffe
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/application.yml.vm
@@ -0,0 +1,124 @@
+#set($dollar = '$')
+spring:
+ application:
+ name: ${dashModuleName}-server
+
+ profiles:
+ active: ${dollar}{env.name}
+ #统一nacos配置,使用 profile 管理
+ cloud:
+ nacos:
+ server-addr: ${dollar}{config.server-addr} # Nacos 服务器地址
+ username: ${dollar}{config.username} # Nacos 账号
+ password: ${dollar}{config.password} # Nacos 密码
+ discovery: # 【配置中心】配置项
+ namespace: ${dollar}{config.namespace} # 命名空间。这里使用 maven Profile 资源过滤进行动态替换
+ group: ${dollar}{config.group} # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
+ metadata:
+ version: 1.0.0 # 服务实例的版本号,可用于灰度发布
+ config: # 【注册中心】配置项
+ namespace: ${dollar}{config.namespace} # 命名空间。这里使用 maven Profile 资源过滤进行动态替换
+ group: ${dollar}{config.group} # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
+ main:
+ allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
+ allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
+
+ config:
+ import:
+ - optional:classpath:application-${dollar}{spring.profiles.active}.yaml # 加载【本地】配置
+ - optional:nacos:${dollar}{spring.application.name}-${dollar}{spring.profiles.active}.yaml # 加载【Nacos】的配置
+
+ # Servlet 配置
+ servlet:
+ # 文件上传相关配置项
+ multipart:
+ max-file-size: 16MB # 单个文件大小
+ max-request-size: 32MB # 设置总上传的文件大小
+
+ # Jackson 配置项
+ jackson:
+ serialization:
+ write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
+ write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
+ write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
+ fail-on-empty-beans: false # 允许序列化无属性的 Bean
+ time-zone: Asia/Shanghai
+
+ # Cache 配置项
+ cache:
+ type: REDIS
+ redis:
+ time-to-live: 1h # 设置过期时间为 1 小时
+
+server:
+ port: ${port}
+
+logging:
+ file:
+ name: ${dollar}{user.home}/logs/${dollar}{spring.application.name}.log # 日志文件名,全路径
+
+springdoc:
+ api-docs:
+ enabled: true # 1. 是否开启 Swagger 接文档的元数据
+ path: /v3/api-docs
+ swagger-ui:
+ enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面
+ path: /swagger-ui.html
+ default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
+
+knife4j:
+ enable: true # 2.2 是否开启 Swagger 文档的 Knife4j UI 界面
+ setting:
+ language: zh_cn
+
+# MyBatis Plus 的配置项
+mybatis-plus:
+ configuration:
+ map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
+ global-config:
+ db-config:
+ id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
+ # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库
+ # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
+ # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
+ logic-delete-value: 1 # 逻辑已删除值(默认为 1)
+ logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+ banner: false # 关闭控制台的 Banner 打印
+ type-aliases-package: ${basePackage}.module.*.dal.dataobject
+ encryptor:
+ password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
+
+mybatis-plus-join:
+ banner: false # 关闭控制台的 Banner 打印
+
+# VO 转换(数据翻译)相关
+easy-trans:
+ is-enable-global: false # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
+
+xxl:
+ job:
+ executor:
+ appname: ${dollar}{spring.application.name} # 执行器 AppName
+ logpath: ${dollar}{user.home}/logs/xxl-job/${dollar}{spring.application.name} # 执行器运行日志文件存储磁盘路径
+ accessToken: default_token # 执行器通讯TOKEN
+
+yudao:
+ info:
+ version: 1.0.0
+ base-package: ${basePackage}.module.${packageName}
+ web:
+ admin-ui:
+ url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址
+ xss:
+ enable: false
+ exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
+ - ${dollar}{spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
+ - ${dollar}{management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
+ swagger:
+ title: 管理后台
+ description: 提供管理员管理的所有功能
+ version: ${dollar}{yudao.info.version}
+ tenant: # 多租户相关配置项
+ enable: true
+
+debug: false
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/pom.xml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/pom.xml.vm
new file mode 100644
index 00000000..729ddd91
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/pom.xml.vm
@@ -0,0 +1,24 @@
+
+
+
+ yudao
+ cn.iocoder.cloud
+ ${revision}
+
+
+ yudao-module-${dashModuleName}-api
+ yudao-module-${dashModuleName}-server
+
+ 4.0.0
+
+ yudao-module-${dashModuleName}
+ pom
+
+ ${project.artifactId}
+
+ ${moduleDescription}。
+
+
+
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/server-pom.xml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/server-pom.xml.vm
new file mode 100644
index 00000000..5acd236a
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/module/server-pom.xml.vm
@@ -0,0 +1,151 @@
+
+
+
+ yudao-module-${dashModuleName}
+ cn.iocoder.cloud
+ ${revision}
+
+ 4.0.0
+ jar
+
+ yudao-module-${dashModuleName}-server
+
+ ${project.artifactId}
+
+ ${moduleDescription}。
+
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-env
+
+
+
+
+ cn.iocoder.cloud
+ yudao-module-system-api
+ ${revision}
+
+
+ cn.iocoder.cloud
+ yudao-module-infra-api
+ ${revision}
+
+
+
+ cn.iocoder.cloud
+ yudao-module-${dashModuleName}-api
+ ${revision}
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-data-permission
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-tenant
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-web
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-security
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mybatis
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-redis
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-rpc
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-job
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mq
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-test
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-excel
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-monitor
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-business
+ ${revision}
+
+
+
+
+
+ ${project.artifactId}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+ repackage
+
+
+
+
+
+
+
+
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Application.java.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Application.java.vm
new file mode 100644
index 00000000..0f91e56f
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Application.java.vm
@@ -0,0 +1,21 @@
+#set($dollar = '$')
+package ${basePackage}.${packageName};
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * ${serverDescription}的启动类
+ *
+ * @author ${author}
+ */
+@SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${dollar}{yudao.info.base-package}
+@SpringBootApplication(scanBasePackages = {"${dollar}{yudao.info.base-package}.${packageName}", "${dollar}{yudao.info.base-package}.module"},
+ excludeName = {})
+public class ${applicationClassName} {
+
+ public static void main(String[] args) {
+ SpringApplication.run(${applicationClassName}.class, args);
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Controller.java.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Controller.java.vm
new file mode 100644
index 00000000..f3181a3b
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Controller.java.vm
@@ -0,0 +1,29 @@
+package ${basePackage}.${packageName}.controller.${baseName};
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import ${basePackage}.framework.common.pojo.CommonResult;
+
+import static ${basePackage}.framework.common.pojo.CommonResult.success;
+
+/**
+ * ${baseName} 控制器
+ *
+ * @author ${author}
+ */
+@Tag(name = "${baseName}")
+@RestController
+@RequestMapping("/${baseName}")
+public class ${capitalizedBaseName}Controller {
+
+ @GetMapping("/hello")
+ @Operation(summary = "Hello ${baseName}")
+ public CommonResult hello() {
+ return success("Hello, ${baseName}!");
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Dockerfile.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Dockerfile.vm
new file mode 100644
index 00000000..baee3299
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/Dockerfile.vm
@@ -0,0 +1,13 @@
+FROM openjdk:17-jre-slim
+
+# 设置应用目录
+WORKDIR /app
+
+# 复制应用文件
+COPY target/${serverName}.jar /app/${serverName}.jar
+
+# 暴露端口
+EXPOSE ${port}
+
+# 运行应用
+ENTRYPOINT ["java", "-jar", "/app/${serverName}.jar"]
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application-dev.yml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application-dev.yml.vm
new file mode 100644
index 00000000..d31287be
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application-dev.yml.vm
@@ -0,0 +1,124 @@
+server:
+ servlet:
+ encoding:
+ enabled: true
+ charset: UTF-8 # 必须设置 UTF-8,避免 WebFlux 流式返回(AI 场景)会乱码问题
+ force: true
+
+--- #################### 数据库相关配置 ####################
+
+spring:
+ # 数据源配置项
+ 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 # 配置检测连接是否有效
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+ # 设置默认的数据源或者数据源组,默认 master
+ primary: master
+ datasource:
+ # 主库
+ master:
+ url: jdbc:mysql://127.0.0.1:3306/${dbName}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
+ username: root
+ password:
+ # 从库
+ slave:
+ lazy: true # 开启懒加载,保证启动速度
+ url: jdbc:mysql://127.0.0.1:3306/${dbName}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
+ username: root
+ password:
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ data:
+ redis:
+ host: 127.0.0.1 # 地址
+ port: 6379 # 端口
+ database: 1 # 数据库索引
+# password: # 密码,建议生产环境开启
+
+--- #################### 定时任务相关配置 ####################
+
+xxl:
+ job:
+ enabled: false # 是否开启调度中心,默认为 true 开启
+ admin:
+ addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
+
+--- #################### 消息队列相关 ####################
+
+# rocketmq 配置项,对应 RocketMQProperties 配置类
+rocketmq:
+ name-server: 127.0.0.1:9876 # RocketMQ Namesrv
+
+spring:
+ # RabbitMQ 配置项,对应 RabbitProperties 配置类
+ rabbitmq:
+ host: 127.0.0.1 # RabbitMQ 服务的地址
+ port: 5672 # RabbitMQ 服务的端口
+ username: guest # RabbitMQ 服务的账号
+ password: guest # RabbitMQ 服务的密码
+ # Kafka 配置项,对应 KafkaProperties 配置类
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+--- #################### 监控相关配置 ####################
+
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# 日志文件配置
+logging:
+ file:
+ name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
+ level:
+ # 配置自己写的 MyBatis Mapper 打印日志
+ ${basePackage}.${packageName}.dal.mysql: debug
+
+--- #################### 芋道相关配置 ####################
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ demo: false # 开启演示模式
+ # 附件加密相关配置
+ AES:
+ key: "0123456789abcdef0123456789abcdef"
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application-local.yml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application-local.yml.vm
new file mode 100644
index 00000000..99f2bfd9
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application-local.yml.vm
@@ -0,0 +1,125 @@
+server:
+ servlet:
+ encoding:
+ enabled: true
+ charset: UTF-8 # 必须设置 UTF-8,避免 WebFlux 流式返回(AI 场景)会乱码问题
+ force: true
+
+--- #################### 数据库相关配置 ####################
+
+spring:
+ # 数据源配置项
+ 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: 1 # 初始连接数
+ min-idle: 1 # 最小连接池数量
+ 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
+ # 设置默认的数据源或者数据源组,默认 master
+ primary: master
+ datasource:
+ # 主库
+ master:
+ url: jdbc:mysql://127.0.0.1:3306/${dbName}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
+ username: root
+ password:
+ # 从库
+ slave:
+ lazy: true # 开启懒加载,保证启动速度
+ url: jdbc:mysql://127.0.0.1:3306/${dbName}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
+ username: root
+ password:
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ data:
+ redis:
+ host: localhost # 地址
+ port: 6379 # 端口
+ database: 1 # 数据库索引
+# password: # 密码,建议生产环境开启
+
+--- #################### 定时任务相关配置 ####################
+
+xxl:
+ job:
+ enabled: false # 是否开启调度中心,默认为 true 开启
+ admin:
+ addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
+
+--- #################### 消息队列相关 ####################
+
+# rocketmq 配置项,对应 RocketMQProperties 配置类
+rocketmq:
+ name-server: localhost:9876 # RocketMQ Namesrv
+
+spring:
+ # RabbitMQ 配置项,对应 RabbitProperties 配置类
+ rabbitmq:
+ host: 127.0.0.1 # RabbitMQ 服务的地址
+ port: 5672 # RabbitMQ 服务的端口
+ username: rabbit # RabbitMQ 服务的账号
+ password: rabbit # RabbitMQ 服务的密码
+ # Kafka 配置项,对应 KafkaProperties 配置类
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+--- #################### 监控相关配置 ####################
+
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# 日志文件配置
+logging:
+ file:
+ name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
+ level:
+ # 配置自己写的 MyBatis Mapper 打印日志
+ ${basePackage}.${packageName}.dal.mysql: debug
+ root: info
+
+--- #################### 芋道相关配置 ####################
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ demo: false # 开启演示模式
+ # 附件加密相关配置
+ AES:
+ key: "0123456789abcdef0123456789abcdef"
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application.yml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application.yml.vm
new file mode 100644
index 00000000..9b5c6241
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/application.yml.vm
@@ -0,0 +1,111 @@
+server:
+ port: ${port}
+
+spring:
+ application:
+ name: ${serverName}
+ main:
+ allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
+ 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
+
+ # Cache 配置项
+ cache:
+ type: REDIS
+ redis:
+ time-to-live: 1h # 设置过期时间为 1 小时
+
+--- #################### 接口文档配置 ####################
+
+springdoc:
+ api-docs:
+ enabled: true
+ path: /v3/api-docs
+ swagger-ui:
+ enabled: true
+ path: /swagger-ui
+ default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
+
+knife4j:
+ enable: true
+ setting:
+ language: zh_cn
+
+# MyBatis Plus 的配置项
+mybatis-plus:
+ configuration:
+ map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
+ global-config:
+ db-config:
+ id-type: NONE # "智能"模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
+ logic-delete-value: 1 # 逻辑已删除值(默认为 1)
+ logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+ banner: false # 关闭控制台的 Banner 打印
+ type-aliases-package: ${basePackage}.${packageName}.dal.dataobject
+
+# Spring Data Redis 配置
+spring:
+ data:
+ redis:
+ repositories:
+ enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
+
+--- #################### 芋道相关配置 ####################
+
+yudao:
+ info:
+ version: 1.0.0
+ base-package: ${basePackage}
+ author: ${author}
+ description: ${serverDescription}
+ web:
+ admin-ui:
+ url: http://localhost:3000 # Admin 管理后台 UI 的地址
+ xss:
+ enable: false
+ security:
+ permit-all_urls: []
+ websocket:
+ enable: true # websocket的开关
+ path: /infra/ws # 路径
+ sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq
+ sender-rocketmq:
+ topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic
+ consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group
+ sender-rabbitmq:
+ exchange: ${spring.application.name}-websocket-exchange # 消息发送的 RabbitMQ Exchange
+ queue: ${spring.application.name}-websocket-queue # 消息发送的 RabbitMQ Queue
+ sender-kafka:
+ topic: ${spring.application.name}-websocket # 消息发送的 Kafka Topic
+ consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 Kafka Consumer Group
+ swagger:
+ title: ${serverName}
+ description: ${serverDescription}
+ version: ${yudao.info.version}
+ email: xingyu4j@vip.qq.com
+ license: MIT
+ license-url: https://gitee.com/zhijiantianya/ruoyi-vue-pro/blob/master/LICENSE
+ codegen:
+ base-package: ${basePackage}.${packageName}
+ db-schemas: ${spring.datasource.dynamic.datasource.master.name}
+ front-type: 20 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类
+ vo-type: 10 # VO 的类型,参见 CodegenVOTypeEnum 枚举类
+ delete-batch-enable: true # 是否生成批量删除接口
+ unit-test-enable: false # 是否生成单元测试
+
+debug: false
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/pom.xml.vm b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/pom.xml.vm
new file mode 100644
index 00000000..ca55513d
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/resources/generator/server/pom.xml.vm
@@ -0,0 +1,99 @@
+
+
+
+ cn.iocoder.cloud
+ yudao
+ ${revision}
+
+ 4.0.0
+ ${serverName}
+ jar
+
+ ${serverName}
+ ${serverDescription}
+
+
+
+ cn.iocoder.cloud
+ yudao-module-system-server
+ ${revision}
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+
+ cn.iocoder.cloud
+ yudao-module-infra-server
+ ${revision}
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-protection
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-rpc
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+
+
+
+
+
+ ${project.artifactId}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+ repackage
+
+
+
+
+
+
+
+