From 287f4bbd7edffe2612dd6547934f4ed977420274 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 2 Sep 2025 09:46:13 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E6=96=B0=E5=A2=9E=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8=E6=94=AF=E6=8C=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=97=B6=E9=A2=84=E8=AE=BE=E5=9F=BA=E7=A1=80=E5=B0=81=E8=A3=85?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=88=E6=9F=A5=E8=AF=A2=E3=80=81=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E3=80=81=E5=88=97=E8=A1=A8=E3=80=81=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=EF=BC=89=202.=20=E6=96=B0=E5=A2=9E=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E4=B8=8E=20Server=20=E6=96=B0=E5=A2=9E=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/core/ut/ModuleGenerator.java | 241 +++++++++++++++++ .../test/core/ut/ServerGenerator.java | 156 +++++++++++ .../resources/codegen/vue3/views/form.vue.vm | 173 ++++-------- .../resources/codegen/vue3/views/index.vue.vm | 256 ++++++++++++------ yudao-server/pom.xml | 6 + .../yudao/server/YudaoServerApplication.java | 22 +- 6 files changed, 624 insertions(+), 230 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ModuleGenerator.java create mode 100644 yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ServerGenerator.java 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 new file mode 100644 index 00000000..045d65e0 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ModuleGenerator.java @@ -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 的 标签中:"); + for (String moduleName : modules) { + moduleName = moduleName.trim(); + if (!moduleName.isEmpty()) { + String dashModuleName = camelToKebabCase(moduleName); + System.out.println("yudao-module-" + dashModuleName + ""); + } + } + } + + 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 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(); + } +} 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 new file mode 100644 index 00000000..1fdb0279 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/ServerGenerator.java @@ -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 的 标签中:"); + System.out.println("" + serverName + ""); + } + + 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 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("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": + 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()); + } +} diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/form.vue.vm index c494c96c..ad7c4019 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/form.vue.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/form.vue.vm @@ -1,12 +1,16 @@