迁移日志

迁移计划

计划把有道云笔记、语雀、CSDN、博客园的笔记全部进行迁移。

迁移记录

2023-03-05

  • ✔️【有道云笔记】Java 基础已全部迁移

2023-03-04

  • ✔️【有道云笔记】前端已全部迁移

迁移脚本

由于有道云笔记中存在图片, 在迁移到本站时, 需要把图片地址从有道云换成阿里云 OSS 地址(有道云笔记的图片需要登录才能进行访问), 也算变相实现笔记的双重备份吧。

本次是借助 Java 脚本来实现的,大致思路如下:

  • 遍历有道云的每一个 .md 文件
  • 读取文件内容, 提取其中的图片
  • 将提取到的图片进行下载, 然后上传到阿里云 OSS
  • 用 OSS 的图片地址替换 .md 中原图片的地址

相关代码如下:

  • pom.xml

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.16.1</version>
        </dependency>
    </dependencies>
    
  • AliyunOSSUtils.java

    public class AliyunOSSUtils {
    
        private static final DateTimeFormatter fileNameFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSS");
    
        private static final DateTimeFormatter directoryFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    
        private static final String DEFAULT_FILE_EXT = "png";
    
        private static final String ENDPOINT = "https://oss-cn-hangzhou.aliyuncs.com";
    
        private static final String ACCESS_KEY = "";
    
        private static final String ACCESS_KEY_SECRET = "";
    
        private static final String BUCKET_NAME = "djfmdresources";
    
        private static final String OBJECT_NAME_PREFIX = "athena";
    
        private static final OSS ossClient;
    
        static {
            ossClient = initOssClient();
        }
    
        private AliyunOSSUtils() { }
    
        private static OSS initOssClient() {
            try {
                return new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY, ACCESS_KEY_SECRET);
            } catch (OSSException e) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + e.getErrorMessage());
                System.out.println("Error Code:" + e.getErrorCode());
                System.out.println("Request ID:" + e.getRequestId());
                System.out.println("Host ID:" + e.getHostId());
            } catch (ClientException e) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + e.getMessage());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
            return null;
        }
    
        public static String upload(String webUrl) throws Exception {
            if (StringUtils.isNullOrEmpty(webUrl)) {
                return null;
            }
            URL url = new URL(webUrl);
            URLConnection urlConnection = url.openConnection();
            urlConnection.setRequestProperty("User-Agent", "Mozilla/4.0");
            // 需要登录有道云笔记才行
            urlConnection.setRequestProperty("Cookie", "");
            String contentType = urlConnection.getContentType();
            String fileExt = DEFAULT_FILE_EXT;
            if (contentType != null && contentType.startsWith("image/")) {
                fileExt = contentType.substring(contentType.lastIndexOf("/") + 1);
            }
            InputStream inputStream = urlConnection.getInputStream();
    
            // 创建 PutObjectRequest 对象
            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, getNewFileFullPathName(fileExt), inputStream);
            // 设置该属性可以返回 response。如果不设置,则返回的 response 为空
            putObjectRequest.setProcess(Boolean.TRUE.toString());
            // 创建 PutObject 请求
            PutObjectResult result = ossClient.putObject(putObjectRequest);
            // 如果上传成功,则返回 200
            ResponseMessage response = result.getResponse();
            if (response.isSuccessful()) {
                String ossUrl = response.getUri();
                System.out.println("上传后的 oss 地址:" + ossUrl);
                return ossUrl;
            }
            inputStream.close();
            return null;
        }
    
        private static String getNewFileFullPathName(String fileExt) {
            LocalDateTime now = LocalDateTime.now();
            String fileDirectory = directoryFormatter.format(now);
            String newFileName = fileNameFormatter.format(now);
            return OBJECT_NAME_PREFIX + "/" + fileDirectory + "/" + newFileName + "." + fileExt;
        }
    }
    
  • FileUtils.java

    public class FileUtils {
    
        private static final Pattern IMAGE_PATTERN = Pattern.compile("!\\[(?<imageName>.[^!\\]]*)\\]\\((?<imageUrl>.[^\\)]+)\\)");
    
        public static int replaceMdImage(String originFile, String targetFile) throws Exception {
            int count = 0;
            String content = readFile(originFile);
            if (StringUtils.isNullOrEmpty(content)) {
                System.out.println("文件 " + originFile + " 内容为空, 跳过处理!");
                return count;
            }
            StringBuffer sb = new StringBuffer();
            Matcher matcher = IMAGE_PATTERN.matcher(content);
            while (matcher.find()) {
                String mdFullImage = matcher.group(0);
                String imageName = matcher.group("imageName");
                String imageUrl = matcher.group("imageUrl");
                if (StringUtils.isNullOrEmpty(mdFullImage)
                        || StringUtils.isNullOrEmpty(imageUrl)
                        || isAliyunImage(imageUrl)) {
                    continue;
                }
                System.out.println(String.format("开始替换 image: %s, imageUrl: %s", imageName, imageUrl));
                String ossImageUrl = AliyunOSSUtils.upload(imageUrl);
                if (StringUtils.isNullOrEmpty(ossImageUrl)) {
                    continue;
                }
                mdFullImage = mdFullImage.replace(imageUrl, ossImageUrl);
                matcher.appendReplacement(sb, mdFullImage.replace(imageUrl, ossImageUrl).replace(imageName, ossImageUrl.substring(ossImageUrl.lastIndexOf("/") + 1)));
                count++;
            }
            if (count > 0) {
                matcher.appendTail(sb);
                writeFile(sb.toString(), targetFile);
            }
            return count;
        }
    
        private static String readFile(String fileFullPathName) throws IOException {
            StringBuilder contentBilder = new StringBuilder();
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(fileFullPathName), "UTF-8"))) {
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    contentBilder.append(line).append("\r\n");
                }
            } catch (IOException e) {
                throw e;
            }
            String content = contentBilder.toString();
            return content;
        }
    
        private static void writeFile(String content, String targetFile) {
            try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(new File(targetFile)), "UTF-8")) {
                osw.write(content);
                osw.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private static boolean isAliyunImage(String url) {
            return url.contains("aliyuncs.com");
        }
    }
    
  • Migrate.java

    public class Migrate {
    
        private static final String MD_FILE_PATH = "E:\\myproject\\web\\vuepress\\athena\\docs";
    
        private static final AtomicInteger pageCount = new AtomicInteger(0);
    
        private static final AtomicInteger imageCount = new AtomicInteger(0);
    
        private static final String MD_FILE_EXT = ".md";
    
        private static void run() throws Exception {
            List<Path> paths = Files.walk(Paths.get(MD_FILE_PATH)).collect(Collectors.toList());
            for (Path path : paths) {
                String originFile = path.toFile().getAbsolutePath();
                String targetFile = path.toFile().getAbsolutePath().replace(MD_FILE_PATH, MD_FILE_PATH);
    
                try {
                    if (Files.isDirectory(path)) {
                        if (!new File(targetFile).exists()) {
                            Files.createDirectory(Paths.get(targetFile));
                        }
                    } else {
                        if (isMdFile(originFile)) {
                            System.out.println("-----------------------------------------------------------------------------------");
                            int replaceCount = FileUtils.replaceMdImage(originFile, targetFile);
                            imageCount.addAndGet(replaceCount);
                            pageCount.incrementAndGet();
                            System.out.println(targetFile + " 替换完成, 共替换 " + replaceCount + " 处");
                        } else {
                            Files.copy(path, Paths.get(targetFile));
                        }
                    }
                } catch (Exception e) {
                    if (e instanceof FileAlreadyExistsException) {
                        System.out.println("目标文件: " + targetFile + " 已经存在");
                        e.printStackTrace();
                    } else {
                        throw e;
                    }
                }
                TimeUnit.MILLISECONDS.sleep(500);
            }
            System.out.println("-----------------------------------------------------------------------------------");
        }
    
        private static boolean isMdFile(String file) {
            if (StringUtils.isNullOrEmpty(file)) {
                return false;
            }
            return file.lastIndexOf(MD_FILE_EXT) > 0;
        }
    
        public static void main(String[] args) throws Exception {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String start = formatter.format(LocalDateTime.now());
            System.out.println("\n[START-TIMESTAMP] " + start + "\n");
    
            run();
    
            System.out.println("\n迁移完成, 本次迁移文件总数: " + pageCount.get() + ", 迁移图片总数: " + imageCount.get());
            String end = formatter.format(LocalDateTime.now());
            System.out.println("\n[END-TIMESTAMP] " + end + "\n");
        }
    }