|
|
@@ -0,0 +1,217 @@
|
|
|
+package com.vber.reactor.core;
|
|
|
+
|
|
|
+import cn.hutool.core.io.FileTypeUtil;
|
|
|
+import cn.hutool.core.io.FileUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import com.vber.common.core.utils.SetUtils;
|
|
|
+import com.vber.reactor.domain.ProjectInfo;
|
|
|
+import jakarta.validation.constraints.NotNull;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+import static java.io.File.separator;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author Yue
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+public class ProjectReactor {
|
|
|
+ private static final String GROUP_ID = "com.vap";
|
|
|
+ private static final String ARTIFACT_ID = "VberAdminPlus";
|
|
|
+ private static final String VERSION = "V3";
|
|
|
+ private static final String ARTIFACT_ID_VERSION = ARTIFACT_ID + VERSION;
|
|
|
+ private static final String PACKAGE_NAME = "com.vber";
|
|
|
+ private static final String MODULE_PREFIX = "vber-";
|
|
|
+ private static final String MODULE_ARTIFACT_ID_PREFIX = "<artifactId>" + MODULE_PREFIX;
|
|
|
+ private static final String MODULE_ID_PREFIX = "<module>" + MODULE_PREFIX;
|
|
|
+ private static final String TITLE = "VAP后台管理系统";
|
|
|
+ private static final String UI_VUE_FILE = "VAP_V3.VUE";
|
|
|
+ private static final String UI_APP_FILE = "VAP_V3.APP";
|
|
|
+ /**
|
|
|
+ * 白名单文件,不进行重写,避免出问题
|
|
|
+ */
|
|
|
+ private static final Set<String> WHITE_FILE_TYPES = SetUtils.asSet("gif", "jpg", "svg", "png", // 图片
|
|
|
+ "eot", "woff2", "ttf", "woff"); // 字体
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建项目。
|
|
|
+ * 该方法使用提供的参数配置项目信息,并调用另一个方法进行实际的项目构建。
|
|
|
+ *
|
|
|
+ * @param groupId 项目的组ID,标识项目的所属组织或组。
|
|
|
+ * @param artifactId 项目的artifact ID,是项目在组内的唯一标识。
|
|
|
+ * @param packageName 项目的包名,用于Java项目的包结构定义。
|
|
|
+ * @param title 项目的标题,可用于描述项目的名称或目的。
|
|
|
+ * @param projectDir 项目目录的路径,指定项目文件和资源所在的物理位置。
|
|
|
+ * @param modulePrefix 项目模块前缀,如果为空则使用默认值"vber-"。用于构建模块名称。
|
|
|
+ */
|
|
|
+ public static void build(@NotNull String groupId, @NotNull String artifactId, @NotNull String packageName, @NotNull String title, @NotNull String projectDir, String modulePrefix) {
|
|
|
+ // 如果modulePrefix为空,则使用默认前缀
|
|
|
+ modulePrefix = StrUtil.isEmpty(modulePrefix) ? "vber-" : modulePrefix;
|
|
|
+ modulePrefix = modulePrefix.endsWith("-") ? modulePrefix : modulePrefix + "-";
|
|
|
+ // 确保projectDir以反斜杠结尾。
|
|
|
+ projectDir = projectDir.endsWith("\\") ? projectDir : projectDir + "\\";
|
|
|
+ build(new ProjectInfo(groupId, artifactId, packageName, title, projectDir, modulePrefix));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建项目的方法。
|
|
|
+ * 根据提供的项目信息,该方法会创建一个新的项目目录,复制旧项目中的文件到新目录,并根据提供的项目信息修改文件内容。
|
|
|
+ *
|
|
|
+ * @param projectInfo 包含项目的基本信息,如组ID、artifactID、包名等。
|
|
|
+ */
|
|
|
+ public static void build(ProjectInfo projectInfo) {
|
|
|
+ // 记录开始时间
|
|
|
+ long start = System.currentTimeMillis();
|
|
|
+
|
|
|
+ // 分割获取项目的基本目录路径
|
|
|
+ String[] arr = getProjectBaseDir().split("SERVER");
|
|
|
+ String oldProjectDir = arr[0];
|
|
|
+
|
|
|
+ // 使用项目信息中的值,如果为空则使用默认值
|
|
|
+ String groupId = StrUtil.isEmpty(projectInfo.getGroupId()) ? GROUP_ID : projectInfo.getGroupId();
|
|
|
+ String artifactId = StrUtil.isEmpty(projectInfo.getArtifactId()) ? ARTIFACT_ID : projectInfo.getArtifactId();
|
|
|
+ String packageName = StrUtil.isEmpty(projectInfo.getPackageName()) ? PACKAGE_NAME : projectInfo.getPackageName();
|
|
|
+ String modulePrefix = StrUtil.isEmpty(projectInfo.getModulePrefix()) ? MODULE_PREFIX : projectInfo.getModulePrefix();
|
|
|
+ String moduleArtifactIdPrefix = "<artifactId>" + modulePrefix;
|
|
|
+ String moduleIdPrefix = "<module>" + modulePrefix;
|
|
|
+ String uiFile = modulePrefix.replace("-", "").toUpperCase();
|
|
|
+ String title = StrUtil.isEmpty(projectInfo.getTitle()) ? TITLE : projectInfo.getTitle();
|
|
|
+
|
|
|
+ if (StrUtil.isEmpty(projectInfo.getGroupId())) {
|
|
|
+ log.warn("[新项目 GroupId 为空!]");
|
|
|
+ }
|
|
|
+ if (StrUtil.isEmpty(projectInfo.getArtifactId())) {
|
|
|
+ log.warn("[新项目 ArtifactId 为空!]");
|
|
|
+ }
|
|
|
+ if (StrUtil.isEmpty(projectInfo.getPackageName())) {
|
|
|
+ log.warn("[新项目 PackageName 为空!]");
|
|
|
+ }
|
|
|
+ if (StrUtil.isEmpty(projectInfo.getTitle())) {
|
|
|
+ log.warn("[新项目 Title 为空!]");
|
|
|
+ }
|
|
|
+ log.info("[原项目路劲改地址 ({})]", oldProjectDir);
|
|
|
+ String projectDir = StrUtil.isEmpty(projectInfo.getProjectDir()) ? oldProjectDir + "_New\\" :
|
|
|
+ projectInfo.getProjectDir().endsWith("\\") ? projectInfo.getProjectDir() : projectInfo.getProjectDir() + "\\";
|
|
|
+
|
|
|
+ log.info("[检测新项目目录 ({})是否存在]", projectDir);
|
|
|
+ if (FileUtil.exist(projectDir)) {
|
|
|
+ log.error("[新项目目录检测 ({})已存在,请更改新的目录!程序退出]", projectDir);
|
|
|
+ throw new RuntimeException(StrUtil.format("新项目目录检测 ({})已存在,请更改新的目录!", projectDir));
|
|
|
+ }
|
|
|
+ // 如果新目录中存在 PACKAGE_NAME,ARTIFACT_ID 等关键字,路径会被替换,导致生成的文件不在预期目录
|
|
|
+ if (StrUtil.containsAny(projectDir, PACKAGE_NAME, ARTIFACT_ID, StrUtil.upperFirst(ARTIFACT_ID))) {
|
|
|
+ log.error("[新项目目录 `projectBaseDirNew` 检测 ({}) 存在冲突名称「{}」或者「{}」,请更改新的目录!程序退出]",
|
|
|
+ projectDir, PACKAGE_NAME, ARTIFACT_ID);
|
|
|
+ throw new RuntimeException(StrUtil.format("新项目目录检测 ({})存在冲突名称「{}」或者「{}」,请更改新的目录!", projectDir, PACKAGE_NAME, ARTIFACT_ID));
|
|
|
+ }
|
|
|
+ log.info("[完成新项目目录检测,新项目路径地址 ({})]", projectDir);
|
|
|
+ // 获得需要复制的文件
|
|
|
+ log.info("[开始获得需要重写的文件,预计需要 5-15 秒]");
|
|
|
+ Collection<File> files = listFiles(oldProjectDir);
|
|
|
+ log.info("[需要重写的文件数量:{},预计需要 {}-{} 秒]", files.size(), files.size() / 200, files.size() / 100);
|
|
|
+ // 写入文件
|
|
|
+ files.forEach(file -> {
|
|
|
+ // 如果是白名单的文件类型,不进行重写,直接拷贝
|
|
|
+ String fileType = getFileType(file);
|
|
|
+ if (WHITE_FILE_TYPES.contains(fileType)) {
|
|
|
+ copyFile(file, oldProjectDir, projectDir, packageName, artifactId, modulePrefix, uiFile);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 如果非白名单的文件类型,重写内容,在生成文件
|
|
|
+ String content = replaceFileContent(file, groupId, artifactId, packageName, title, moduleArtifactIdPrefix, moduleIdPrefix);
|
|
|
+ writeFile(file, content, oldProjectDir, projectDir, packageName, artifactId, modulePrefix, uiFile);
|
|
|
+ });
|
|
|
+ log.info("[重写完成]共耗时:{} 秒", (System.currentTimeMillis() - start) / 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Collection<File> listFiles(String projectBaseDir) {
|
|
|
+ Collection<File> files = FileUtil.loopFiles(projectBaseDir);
|
|
|
+ // 移除 IDEA、Git 自身的文件、Node 编译出来的文件
|
|
|
+ files = files.stream()
|
|
|
+ .filter(file -> !file.getPath().contains(separator + "target" + separator)
|
|
|
+ && !file.getPath().contains(separator + "node_modules" + separator)
|
|
|
+ && !file.getPath().contains(separator + ".idea" + separator)
|
|
|
+ && !file.getPath().contains(separator + ".git" + separator)
|
|
|
+ && !file.getPath().contains(separator + "dist" + separator)
|
|
|
+ && !file.getPath().contains(separator + ".vscode" + separator)
|
|
|
+ && !file.getPath().contains(separator + ".vs" + separator)
|
|
|
+ && !file.getPath().contains(separator + "vber" + separator + "logs" + separator)
|
|
|
+ && !file.getPath().contains(separator + "vber" + separator + "profile" + separator)
|
|
|
+ && !file.getPath().contains(separator + "com" + separator + "vber" + separator + "reactor" + separator)
|
|
|
+ && !file.getPath().contains(".flattened-pom.xml")
|
|
|
+ && !file.getPath().contains("dist.zip")
|
|
|
+ && !file.getPath().contains("dist.rar")
|
|
|
+ && !file.getPath().contains(".iml")
|
|
|
+ && !file.getPath().contains(".lock")
|
|
|
+ && !file.getPath().contains(".html.gz"))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ return files;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private static void copyFile(File file, String projectBaseDir, String projectBaseDirNew, String packageNameNew, String artifactIdNew, String modulePrefix, String uiFile) {
|
|
|
+ String newPath = buildNewFilePath(file, projectBaseDir, projectBaseDirNew, packageNameNew, artifactIdNew, modulePrefix, uiFile);
|
|
|
+ FileUtil.copyFile(file, new File(newPath));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String replaceFileContent(File file, String groupIdNew,
|
|
|
+ String artifactIdNew, String packageNameNew,
|
|
|
+ String titleNew, String moduleArtifactIdPrefix, String moduleIdSuffix) {
|
|
|
+ String content = FileUtil.readString(file, StandardCharsets.UTF_8);
|
|
|
+ // 如果是白名单的文件类型,不进行重写
|
|
|
+ String fileType = getFileType(file);
|
|
|
+ if (WHITE_FILE_TYPES.contains(fileType)) {
|
|
|
+ return content;
|
|
|
+ }
|
|
|
+ // 执行文件内容都重写
|
|
|
+ return content.replaceAll(GROUP_ID, groupIdNew)
|
|
|
+ .replaceAll(PACKAGE_NAME, packageNameNew)
|
|
|
+ .replaceAll(MODULE_ARTIFACT_ID_PREFIX, moduleArtifactIdPrefix)
|
|
|
+ .replaceAll(ARTIFACT_ID_VERSION, artifactIdNew) // 带版本的ARTIFACT_ID也替换成无版本的!
|
|
|
+ .replaceAll(ARTIFACT_ID, artifactIdNew) // 必须放在最后替换,因为 ARTIFACT_ID 太短!
|
|
|
+ .replaceAll(StrUtil.upperFirst(ARTIFACT_ID), StrUtil.upperFirst(artifactIdNew))
|
|
|
+ .replaceAll(StrUtil.upperFirst(MODULE_ID_PREFIX), StrUtil.upperFirst(moduleIdSuffix))
|
|
|
+ .replaceAll("permission: \"no_auth\" //NEW PROJECT", "permission: \"\"") //隐藏生成新项目按钮
|
|
|
+ .replaceAll(TITLE, titleNew);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private static void writeFile(File file, String fileContent, String projectBaseDir, String projectBaseDirNew, String packageNameNew, String artifactIdNew, String modulePrefix, String uiFile) {
|
|
|
+ String newPath = buildNewFilePath(file, projectBaseDir, projectBaseDirNew, packageNameNew, artifactIdNew, modulePrefix, uiFile);
|
|
|
+ FileUtil.writeUtf8String(fileContent, newPath);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private static String buildNewFilePath(File file, String projectBaseDir, String projectBaseDirNew, String packageNameNew, String artifactIdNew, String modulePrefix, String uiFile) {
|
|
|
+ String uiVueFile = uiFile + ".VUE";
|
|
|
+ String uiAppFile = uiFile + ".APP";
|
|
|
+ return file.getPath().replace(projectBaseDir, projectBaseDirNew) // 新目录
|
|
|
+ .replace(PACKAGE_NAME.replaceAll("\\.", Matcher.quoteReplacement(separator)),
|
|
|
+ packageNameNew.replaceAll("\\.", Matcher.quoteReplacement(separator)))
|
|
|
+ .replace(ARTIFACT_ID, artifactIdNew) //
|
|
|
+ .replace(MODULE_PREFIX, modulePrefix) //
|
|
|
+ .replace(UI_VUE_FILE, uiVueFile) //
|
|
|
+ .replace(UI_APP_FILE, uiAppFile) //
|
|
|
+ .replaceAll(StrUtil.upperFirst(ARTIFACT_ID), StrUtil.upperFirst(artifactIdNew));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String getFileType(File file) {
|
|
|
+ return file.length() > 0 ? FileTypeUtil.getType(file) : "";
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String getProjectBaseDir() {
|
|
|
+ String baseDir = System.getProperty("user.dir");
|
|
|
+ if (StrUtil.isEmpty(baseDir)) {
|
|
|
+ throw new NullPointerException("项目基础路径不存在");
|
|
|
+ }
|
|
|
+ return baseDir;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|