1. 新增代码生成器支持生成时预设基础封装组件(查询、表格、列表、编辑弹窗)
2. 新增模块与 Server 新增脚本方法
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
package cn.iocoder.yudao.framework.test.core.ut;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Scanner;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Yudao 模块代码生成器
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 在 IDE 中,右键运行 {@link #main(String[])} 方法
|
||||
* 2. 根据提示,输入模块名,例如 "order"
|
||||
* 3. 根据提示,输入作者名,例如 "yudao"
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class ModuleGenerator {
|
||||
|
||||
private static final String TEMPLATE_PATH = "yudao-module-template";
|
||||
private static final String BASE_PACKAGE = "cn.iocoder.yudao.module.";
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
// 1. 获取用户输入
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.print("请输入新模块的名称(支持多个模块,用逗号分割,例如:order,userManagement,payment): ");
|
||||
String moduleNames = scanner.nextLine();
|
||||
System.out.print("请输入作者名称(例如:yudao): ");
|
||||
String author = scanner.nextLine();
|
||||
scanner.close();
|
||||
|
||||
// 分割模块名
|
||||
String[] modules = moduleNames.split(",");
|
||||
|
||||
// 2. 定义项目根路径
|
||||
Path projectRoot = Paths.get("").toAbsolutePath();
|
||||
Path templateDir = projectRoot.resolve(TEMPLATE_PATH);
|
||||
|
||||
if (!Files.exists(templateDir)) {
|
||||
System.err.println("错误:模板目录不存在: " + templateDir);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 批量创建模块
|
||||
for (String moduleName : modules) {
|
||||
moduleName = moduleName.trim(); // 去除空格
|
||||
if (moduleName.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
System.out.println("\n=== 开始创建模块: " + moduleName + " ===");
|
||||
createSingleModule(templateDir, projectRoot, moduleName, author);
|
||||
}
|
||||
|
||||
System.out.println("\n所有模块创建完成!");
|
||||
System.out.println("请手动将以下模块添加到根 pom.xml 的 <modules> 标签中:");
|
||||
for (String moduleName : modules) {
|
||||
moduleName = moduleName.trim();
|
||||
if (!moduleName.isEmpty()) {
|
||||
String dashModuleName = camelToKebabCase(moduleName);
|
||||
System.out.println("<module>yudao-module-" + dashModuleName + "</module>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createSingleModule(Path templateDir, Path projectRoot, String moduleName, String author) throws IOException {
|
||||
String packageName = moduleName.replace("-", "");
|
||||
String capitalizedModuleName = capitalize(packageName);
|
||||
|
||||
// 将小驼峰转换为短横线分割的模块名
|
||||
String dashModuleName = camelToKebabCase(moduleName);
|
||||
|
||||
// 定义新模块路径
|
||||
Path newModuleDir = projectRoot.resolve("yudao-module-" + dashModuleName);
|
||||
|
||||
if (Files.exists(newModuleDir)) {
|
||||
System.err.println("警告:模块 '" + dashModuleName + "' 已存在于 " + newModuleDir + ",跳过创建");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("将在以下位置创建新模块: " + newModuleDir);
|
||||
|
||||
// 复制并处理文件
|
||||
copyAndProcessDirectory(templateDir, newModuleDir, dashModuleName, packageName, capitalizedModuleName, author);
|
||||
|
||||
// 创建启动类
|
||||
createApplicationClass(newModuleDir, dashModuleName, packageName, capitalizedModuleName, author);
|
||||
|
||||
System.out.println("模块 '" + dashModuleName + "' 创建成功!");
|
||||
}
|
||||
|
||||
private static void copyAndProcessDirectory(Path sourceDir, Path targetDir, String moduleName, String packageName, String capitalizedModuleName, String author) throws IOException {
|
||||
try (Stream<Path> 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();
|
||||
|
||||
// 替换路径中的 '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);
|
||||
|
||||
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);
|
||||
Files.write(targetPath, newContent.getBytes());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("处理文件失败: " + sourcePath, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldIncludePath(Path relativePath) {
|
||||
String pathStr = relativePath.toString();
|
||||
|
||||
// 排除 target 目录
|
||||
if (pathStr.contains("target")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 排除 .flattened-pom.xml 文件
|
||||
if (pathStr.contains(".flattened-pom.xml")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果是根目录或者包含 example 的路径,则包含
|
||||
if (relativePath.getNameCount() <= 3) { // 根目录和基础包结构
|
||||
return true;
|
||||
}
|
||||
|
||||
// 只包含 example 相关的文件和目录
|
||||
return pathStr.contains("example") || pathStr.contains("ErrorCodeConstants");
|
||||
}
|
||||
|
||||
private static String processFileContent(String content, String fileName, String moduleName, String packageName, String capitalizedModuleName, String author) {
|
||||
// 如果是 ErrorCodeConstants 文件,特殊处理
|
||||
if (fileName.equals("ErrorCodeConstants.java")) {
|
||||
return processErrorCodeConstants(content, moduleName, packageName, author);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package cn.iocoder.yudao.framework.test.core.ut;
|
||||
|
||||
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.Scanner;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Yudao Server 代码生成器
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 在 IDE 中,右键运行 {@link #main(String[])} 方法
|
||||
* 2. 根据提示,输入新 Server 的名称,例如 "demo-server"
|
||||
* 3. 根据提示,输入作者名,例如 "yudao"
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class ServerGenerator {
|
||||
|
||||
private static final String TEMPLATE_PATH = "yudao-server";
|
||||
private static final String BASE_PACKAGE_PREFIX = "cn.iocoder.yudao.";
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
// 1. 获取用户输入
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.print("请输入新 Server 的基础名称(小写短横线,例如:demo): ");
|
||||
String baseName = scanner.nextLine();
|
||||
String serverName = baseName + "-server";
|
||||
System.out.print("请输入作者名称(例如:yudao): ");
|
||||
String author = scanner.nextLine();
|
||||
scanner.close();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
System.out.println("\n=== 开始创建 Server: " + serverName + " ===");
|
||||
createSingleServer(templateDir, projectRoot, serverName, author);
|
||||
|
||||
System.out.println("\nServer '" + serverName + "' 创建成功!");
|
||||
System.out.println("请手动将以下模块添加到根 pom.xml 的 <modules> 标签中:");
|
||||
System.out.println("<module>" + serverName + "</module>");
|
||||
}
|
||||
|
||||
private static void createSingleServer(Path templateDir, Path projectRoot, String serverName, String author) throws IOException {
|
||||
String packageName = serverName.replace("-", "");
|
||||
String capitalizedName = toPascalCase(serverName);
|
||||
|
||||
// 定义新 Server 路径
|
||||
Path newServerDir = projectRoot.resolve(serverName);
|
||||
|
||||
if (Files.exists(newServerDir)) {
|
||||
System.err.println("警告:Server '" + serverName + "' 已存在于 '" + newServerDir + "',跳过创建。");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("将在以下位置创建新 Server: " + newServerDir);
|
||||
|
||||
// 复制并处理文件
|
||||
copyAndProcessDirectory(templateDir, newServerDir, serverName, packageName, capitalizedName, author);
|
||||
}
|
||||
|
||||
private static void copyAndProcessDirectory(Path sourceDir, Path targetDir, String serverName, String packageName, String capitalizedName, String author) throws IOException {
|
||||
try (Stream<Path> stream = Files.walk(sourceDir)) {
|
||||
stream.forEach(sourcePath -> {
|
||||
try {
|
||||
Path relativePath = sourceDir.relativize(sourcePath);
|
||||
if (!shouldIncludePath(relativePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理路径和文件名
|
||||
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");
|
||||
|
||||
Path targetPath = targetDir.resolve(targetPathStr);
|
||||
|
||||
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);
|
||||
Files.write(targetPath, newContent.getBytes());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("处理文件失败: " + sourcePath, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
String newContent = content.replace("芋道源码", author);
|
||||
|
||||
switch (fileName) {
|
||||
case "pom.xml":
|
||||
// 替换 artifactId
|
||||
newContent = newContent.replace("<artifactId>yudao-server</artifactId>", "<artifactId>" + serverName + "</artifactId>");
|
||||
// 移除对 yudao-module-xxx-server 的依赖,但保留 system-server 和 infra-server
|
||||
return newContent.replaceAll("(?m)^\\s*<dependency>\\s*<groupId>cn\\.iocoder\\.cloud</groupId>\\s*<artifactId>yudao-module-(?!system-server|infra-server).*-server</artifactId>[\\s\\S]*?</dependency>\\s*", "");
|
||||
case "application.yaml":
|
||||
return newContent.replace("name: yudao-server", "name: " + serverName);
|
||||
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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user