当前位置: 首页 > article >正文

加餐 —— Spring Boot 项目转 Solon 项目工具

说明

公司里面原来的项目都是 Spring Boot 和 Spring Cloud 框架的,自己手动迁移完一个项目后,发现迁移的过程有些还是能代码化的东西,于是整理了 SpringConverter 这个工具。

这个工具不是说你转换完就能无痛的启动,你还是需要手动处理一些错误。虽然工具内置了一部分的 Spring 与 Solon 的对照关系,但你仍然可能需要修改这个工具的代码,配置遗漏的对照关系,以便能将自己的项目进行迁移。

思路

在手动替换的过程中,会首先创建新项目,然后把旧代码复制到新项目中,之后对相关的包和类进行替换,然后逐步修复其中的错误。

这个工具做的事情就是从实现了复制代码和替换包名、类名的过程。这个过程当中,我发现通常是一个包对另一个包,一个类对一个类,当然也会有多对一的情况,和无法对应的情况,于是注意的工作就是配置类名、包名的映射关系,然后就可以通过代码实现自动替换,从而实现减少工作量的目的。

技巧

  • 自己手动转换 pom 或者 gradle 文件,调整好依赖,不要交给转换工具。
  • 替换原项目地址,原项目包名,新项目地址,新项目包名。
  • 转化工具放置在另一个项目(或者模块中),这样方便多次转换新项目(边调整映射配置边替换)。必要时也可以方便的整个删除新项目。
  • 如果类名从短变长(通常是后缀相同),需要先替换类名,再通过旧包名+新类名的方式,替换新包名。
  • 如果无法对应的包,可以直接替换为空串或者回车(\n)
  • 如果是类上无法使用注解,可以增加回车(\n),替换从空串或者回车(\n)。
  • 注意字符串匹配是通过正则匹配的,替换的原字符串中包含中括弧(()),大括号({}),星号(*)等时需要进行转移。
  • 根据不同的类库进行替换,方便维护。必要时可以自己修改成配置文件从配置文件中读取对照关系。

项目地址

https://gitee.com/CrazyAirhead/porpoise-demo/tree/ray-admin/

ray-tools/ray-spring-converter

代码

代码本身无特别之处,就是根据配置的信息不断地替换源代码。

package com.goldsyear.ray.converter;

import static java.io.File.separator;

import com.jfinal.kit.Kv;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.collection.set.SetUtil;
import org.dromara.hutool.core.io.file.FileTypeUtil;
import org.dromara.hutool.core.io.file.FileUtil;

/**
 * SpringBoot 项目迁移 Solon 项目工具,并不能迁移完就直接启动,只是减少迁移工作量
 *
 * @author Airhead
 */
@Slf4j
public class RayConverterApp {

  /** 白名单文件,不进行重写,避免出问题 */
  private static final Set<String> WHITE_FILE_TYPES =
      SetUtil.of("gif", "jpg", "svg", "png", "eot", "woff2", "ttf", "woff");

  public static void main(String[] args) {
    long start = System.currentTimeMillis();

    // 修改路径
    String oldProjectDir = "旧项目路";
    String oldPackageName = "旧包名";
    String newProjectDir = "新项目路";
    String newPackageName = "新包名";

    log.info("原项目目录:{}, 原基础包名:{}", oldProjectDir, oldPackageName);
    log.info("新项目目录:{}, 新基础包名:{}", newProjectDir, newPackageName);

    // ========== 配置,需要你手动修改 ==========
    List<Kv> mappingList = new ArrayList<>();
    mappingList.add(Kv.by("old", oldPackageName).set("new", newPackageName));

    // Spring 包调整
    mappingSpringBoot(mappingList);

    // Hutool 包调整
    mappingHutool(mappingList);

    // FastJson
    mappingFastJson(mappingList);

    // Mybatis 包调整
    mappingMybatis(mappingList);

    // TODO: 根据自己依赖的项目模块进行替换

    // 业务包调整
    mappingBusiness(mappingList);

    // 获得需要复制的文件
    log.info("开始获得需要重写的文件,预计需要 10-20 秒");
    Collection<File> files = listFiles(oldProjectDir);
    log.info("需要重写的文件数量:{},预计需要 15-30 秒", files.size());

    // 写入文件
    files.forEach(
        file -> {
          // 如果是白名单的文件类型,不进行重写,直接拷贝
          String fileType = FileTypeUtil.getType(file);
          if (WHITE_FILE_TYPES.contains(fileType)) {
            copyFile(file, oldProjectDir, oldPackageName, newProjectDir, newPackageName);
            return;
          }

          // 如果非白名单的文件类型,重写内容,在生成文件
          String content = replaceFileContent(file, mappingList);

          writeFile(file, content, oldProjectDir, oldPackageName, newProjectDir, newPackageName);
        });

    log.info("重写完成,共耗时:{} 秒", (System.currentTimeMillis() - start) / 1000);
  }

  private static void mappingBusiness(List<Kv> mappingList) {
    // TODO
  }

  private static void mappingFastJson(List<Kv> mappingList) {
    // TODO
  }

  private static void mappingMybatis(List<Kv> mappingList) {
    // Page
    mappingList.add(
        Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.pagination.Page")
            .set("new", "com.baomidou.mybatisplus.solon.plugins.pagination.Page"));
    // Model
    mappingList.add(
        Kv.by("old", "com.baomidou.mybatisplus.extension.activerecord.Model")
            .set("new", "com.baomidou.mybatisplus.solon.activerecord.Model"));

    // TenantLineHandler
    mappingList.add(
        Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler")
            .set("new", "com.baomidou.mybatisplus.solon.plugins.handler.TenantLineHandler"));
    // MybatisPlusInterceptor
    mappingList.add(
        Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor")
            .set("new", "com.baomidou.mybatisplus.solon.plugins.MybatisPlusInterceptor"));
    // PaginationInnerInterceptor
    mappingList.add(
        Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor")
            .set("new", "com.baomidou.mybatisplus.solon.plugins.inner.PaginationInnerInterceptor"));
    // TenantLineInnerInterceptor
    mappingList.add(
        Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor")
            .set("new", "com.baomidou.mybatisplus.solon.plugins.inner.TenantLineInnerInterceptor"));

    // FastjsonTypeHandler
    mappingList.add(
        Kv.by("old", "com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler")
            .set("new", "com.baomidou.mybatisplus.solon.handlers.FastjsonTypeHandler"));
  }

  private static void mappingHutool(List<Kv> mappingList) {
    // StrUtil;
    mappingList.add(
        Kv.by("old", "cn.hutool.core.util.StrUtil")
            .set("new", "org.dromara.hutool.core.text.StrUtil"));

    // Convert;
    mappingList.add(
        Kv.by("old", "cn.hutool.core.convert.Convert")
            .set("new", "org.dromara.hutool.core.convert.ConvertUtil"));
    mappingList.add(Kv.by("old", "Convert\\.").set("new", "ConvertUtil."));

    // IdUtil;
    mappingList.add(
        Kv.by("old", "cn.hutool.core.util.IdUtil")
            .set("new", "org.dromara.hutool.core.data.id.IdUtil"));

    // DateUtil;
    mappingList.add(
        Kv.by("old", "cn.hutool.core.date.DateUtil")
            .set("new", "org.dromara.hutool.core.date.DateUtil"));

    // ReUtil
    mappingList.add(
        Kv.by("old", "cn.hutool.core.util.ReUtil")
            .set("new", "org.dromara.hutool.core.regex.ReUtil"));

    // Spring CollectionUtils;
    mappingList.add(
        Kv.by("old", "org.springframework.util.CollectionUtils")
            .set("new", "org.dromara.hutool.core.collection.CollUtil"));
    mappingList.add(Kv.by("old", "CollectionUtils").set("new", "CollUtil"));

    // CollectionUtil;
    mappingList.add(
        Kv.by("old", "cn.hutool.core.collection.CollectionUtil")
            .set("new", "org.dromara.hutool.core.collection.CollUtil"));
    mappingList.add(Kv.by("old", "CollectionUtil").set("new", "CollUtil"));

    // LocalDateTimeUtil
    mappingList.add(
        Kv.by("old", "cn.hutool.core.date.LocalDateTimeUtil")
            .set("new", "org.dromara.hutool.core.date.TimeUtil"));
    mappingList.add(Kv.by("old", "LocalDateTimeUtil").set("new", "TimeUtil"));

    // FileUtil
    mappingList.add(
        Kv.by("old", "cn.hutool.core.io.FileUtil")
            .set("new", "org.dromara.hutool.core.io.file.FileUtil"));

    // IoUtil
    mappingList.add(
        Kv.by("old", "cn.hutool.core.io.IoUtil").set("new", "org.dromara.hutool.core.io.IoUtil"));
  }

  private static void mappingSpringBoot(List<Kv> mappingList) {
    // annotation
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation\\.\\*")
            .set("new", "org.noear.solon.annotation.*"));

    // Component
    mappingList.add(
        Kv.by("old", "org.springframework.stereotype.Component")
            .set("new", "org.noear.solon.annotation.Component"));

    // Service
    mappingList.add(
        Kv.by("old", "org.springframework.stereotype.Service")
            .set("new", "org.noear.solon.annotation.Component"));
    mappingList.add(Kv.by("old", "@Service").set("new", "@Component"));

    // Autowired
    mappingList.add(
        Kv.by("old", "org.springframework.beans.factory.annotation.Autowired")
            .set("new", "org.noear.solon.annotation.Inject"));
    mappingList.add(Kv.by("old", "@Autowired").set("new", "@Inject"));

    // Controller
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.RestController")
            .set("new", "org.noear.solon.annotation.Controller"));
    mappingList.add(Kv.by("old", "@RestController").set("new", "@Controller"));

    // RequestMapping
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.RequestMapping")
            .set("new", "org.noear.solon.annotation.Mapping"));
    mappingList.add(Kv.by("old", "@RequestMapping").set("new", "@Mapping"));

    // MediaType;
    mappingList.add(Kv.by("old", "import org.springframework.http.MediaType;").set("new", ""));
    mappingList.add(
        Kv.by("old", ", produces = MediaType.APPLICATION_JSON_UTF8_VALUE").set("new", ""));

    mappingList.add(
        Kv.by("old", "import org.springframework.core.env.Environment;").set("new", ""));

    // PostMapping
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.PostMapping")
            .set("new", "org.noear.solon.annotation.Post"));
    mappingList.add(Kv.by("old", "@PostMapping").set("new", "@Post\n@Mapping"));

    // GetMapping
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.GetMapping")
            .set("new", "org.noear.solon.annotation.Get"));
    mappingList.add(Kv.by("old", "@GetMapping").set("new", "@Post\n@Mapping"));

    // PutMapping
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.PutMapping")
            .set("new", "org.noear.solon.annotation.Put"));
    mappingList.add(Kv.by("old", "@PutMapping").set("new", "@Put\n@Mapping"));

    // DeleteMapping
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.DeleteMapping")
            .set("new", "org.noear.solon.annotation.Delete"));
    mappingList.add(Kv.by("old", "@DeleteMapping").set("new", "@Delete\n@Mapping"));

    // @RequestBody
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.RequestBody")
            .set("new", "org.noear.solon.annotation.Body"));
    mappingList.add(Kv.by("old", "@RequestBody").set("new", "@Body"));

    // @RequestParam
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.RequestParam")
            .set("new", "org.noear.solon.annotation.Param"));
    mappingList.add(Kv.by("old", "@RequestParam").set("new", "@Param"));

    //    RequestPart
    mappingList.add(
        Kv.by("old", "org.springframework.web.bind.annotation.RequestPart")
            .set("new", "org.noear.solon.annotation.Param"));
    mappingList.add(Kv.by("old", "@RequestPart").set("new", "@Param"));

    // Repository;
    mappingList.add(
        Kv.by("old", "import org.springframework.stereotype.Repository;").set("new", ""));
    mappingList.add(Kv.by("old", "@Repository").set("new", ""));

    // Validated
    mappingList.add(
        Kv.by("old", "org.springframework.validation.annotation.Validated")
            .set("new", "org.noear.solon.validation.annotation.Validated"));
    mappingList.add(Kv.by("old", "@Validated\n").set("new", ""));

    // Configuration;
    mappingList.add(
        Kv.by("old", "org.springframework.context.annotation.Configuration")
            .set("new", "org.noear.solon.annotation.Configuration"));

    // RefreshScope;
    mappingList.add(
        Kv.by("old", "import org.springframework.cloud.context.config.annotation.RefreshScope;")
            .set("new", ""));
    mappingList.add(Kv.by("old", "@RefreshScope\n").set("new", ""));

    // EnableAsync;
    mappingList.add(
        Kv.by("old", "import org.springframework.scheduling.annotation.EnableAsync;")
            .set("new", ""));
    mappingList.add(Kv.by("old", "@EnableAsync").set("new", ""));

    // Bean;
    mappingList.add(
        Kv.by("old", "org.springframework.context.annotation.Bean")
            .set("new", "org.noear.solon.annotation.Bean"));

    //  ConfigurationProperties;
    mappingList.add(
        Kv.by("old", "org.springframework.boot.context.properties.ConfigurationProperties")
            .set("new", "org.noear.solon.annotation.Inject"));
    mappingList.add(Kv.by("old", "@ConfigurationProperties").set("new", "@Inject"));

    // CommandLineRunner
    mappingList.add(
        Kv.by("old", "import org.springframework.boot.CommandLineRunner;")
            .set(
                "new",
                "import org.noear.solon.core.event.AppLoadEndEvent;\nimport org.noear.solon.core.event.EventListener;"));
    mappingList.add(Kv.by("old", "CommandLineRunner").set("new", "EventListener<AppLoadEndEvent>"));

    // Resource;
    mappingList.add(
        Kv.by("old", "javax.annotation.Resource").set("new", "org.noear.solon.annotation.Inject"));
    mappingList.add(Kv.by("old", "@Resource\\(name").set("new", "@Inject\\(value"));

    // Lazy;
    mappingList.add(
        Kv.by("old", "import org.springframework.context.annotation.Lazy;\n").set("new", ""));
    mappingList.add(Kv.by("old", "@Lazy").set("new", ""));

    // Valid
    mappingList.add(
        Kv.by("old", "javax.validation.Valid")
            .set("new", "org.noear.solon.validation.annotation.Valid"));
    mappingList.add(
        Kv.by("old", "javax.validation.constraints.NotEmpty")
            .set("new", "org.noear.solon.validation.annotation.NotEmpty"));
    mappingList.add(
        Kv.by("old", "javax.validation.constraints.NotNull")
            .set("new", "org.noear.solon.validation.annotation.NotNull"));
    // TODO: 补充更多

    // HttpServletResponse;
    mappingList.add(
        Kv.by("old", "javax.servlet.http.HttpServletResponse")
            .set("new", "org.noear.solon.core.handle.Context"));
    mappingList.add(Kv.by("old", "HttpServletResponse").set("new", "Context"));

    // MultipartFile;
    mappingList.add(
        Kv.by("old", "org.springframework.web.multipart.MultipartFile")
            .set("new", "org.noear.solon.core.handle.UploadedFile"));
    mappingList.add(Kv.by("old", "MultipartFile").set("new", "UploadedFile"));

    //    consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}
    mappingList.add(
        Kv.by("old", ",\n\\s+consumes = \\{MediaType.MULTIPART_FORM_DATA_VALUE\\}").set("new", ""));
    mappingList.add(
        Kv.by("old", ", consumes = \\{MediaType.MULTIPART_FORM_DATA_VALUE\\}").set("new", ""));
  }

  private static Collection<File> listFiles(String projectBaseDir) {
    Collection<File> files = FileUtil.loopFiles(new File(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 + "build" + separator)
                        && !file.getPath().contains(separator + "out" + separator)
                        && !file.getPath().contains(".iml")
                        && !file.getPath().contains(".html.gz")
                        && !file.getPath().contains("build.gradle")
                        && !file.getPath().contains("pom.xml"))
            .collect(Collectors.toList());
    return files;
  }

  private static String replaceFileContent(File file, List<Kv> mappingList) {
    String content = FileUtil.readString(file, StandardCharsets.UTF_8);
    // 如果是白名单的文件类型,不进行重写
    String fileType = FileTypeUtil.getType(file);
    if (WHITE_FILE_TYPES.contains(fileType)) {
      return content;
    }

    for (Kv kv : mappingList) {
      content = content.replaceAll(kv.getStr("old"), kv.getStr("new"));
    }

    return content;
  }

  private static void writeFile(
      File file,
      String fileContent,
      String oldProjectDir,
      String oldPageName,
      String newProjectDir,
      String newPackageName) {
    String newPath =
        buildNewFilePath(file, oldProjectDir, oldPageName, newProjectDir, newPackageName);
    FileUtil.writeUtf8String(fileContent, newPath);
  }

  private static void copyFile(
      File file,
      String oldProjectDir,
      String oldPackageName,
      String newProjectDir,
      String newPackageName) {
    String newPath =
        buildNewFilePath(file, oldProjectDir, oldPackageName, newProjectDir, newPackageName);
    FileUtil.copy(file, new File(newPath), true);
  }

  private static String buildNewFilePath(
      File file,
      String oldProjectDir,
      String oldPackageName,
      String newProjectDir,
      String newPackageName) {
    return file.getPath()
        .replace(oldProjectDir, newProjectDir)
        .replace(
            oldPackageName.replaceAll("\\.", Matcher.quoteReplacement(separator)),
            newPackageName.replaceAll("\\.", Matcher.quoteReplacement(separator)));
  }
}

注意

再次提醒这个工具不是无缝迁移的,也就是说不能说迁移完就可以直接启动的,这个工具只是减少工作量。


http://www.kler.cn/a/577973.html

相关文章:

  • MES机联网4:文档资料
  • Go学习笔记:基础语法9
  • 机器学习·NLP中的文本分类
  • Python定时任务管理器
  • 微博热点信息爬虫
  • Java链接redis
  • tomcat的安装与配置(包含在idea中配置tomcat)
  • 景联文科技:以精准数据标注赋能AI进化,构筑智能时代数据基石
  • RAG技术的PDF智能问答系统
  • 【图像阈值分割、区域分割、边缘分割】
  • 计算机毕业设计SpringBoot+Vue.js高校专业实习管理系统(源码+文档+PPT+讲解)
  • CentOS Docker 安装指南
  • mounted() 钩子函数
  • 大数据学习(58)-DolphinScheduler使用DataX实现数据同步
  • 如何在DigitalOcean的H100 GPU服务器上运行DeepSeek R1 模型
  • leetcode日记(85)验证二叉搜索树
  • 深度学习驱动的跨行业智能化革命:技术突破与实践创新
  • 鸿蒙Next-应用检测、安装以及企业内部商店的实现
  • 常见的算法题python
  • SecureCRT 文件上传下载操作指南