跳至主要內容

markdown中图片url替换

soulballad总结文字总结文字总结约 2583 字大约 9 分钟

本地md文件默认路径为: E:\笔记\自己整理\typora-user-images。如果要在非本地访问,需要将其保存到 gitee 等图床上。

在保存到云床之前,需要对文件重命名并进行分组,类似如下效果:

重命名(根据创建日期):2267589-d469f4d79081582a.png -> 202006252052369002.png

日期分组:img/2020/06/25/202006252052369002.png

为了能替换 md 文件中图片地址,必须知道文件的旧名字和新名字,保存在 name.txt

具体实现分如下几步:

1、将原有图片文件重命名复制到新路径,方便操作和恢复

private static final String OLD_PIC_PATH = "E:\\笔记\\自己整理\\test-md\\old-pic\\";
private static final String MID_PIC_PATH = "E:\\笔记\\自己整理\\test-md\\old-pic2\\";
private static final String NEW_PIC_PATH = "E:\\笔记\\自己整理\\test-md\\new-pic\\";

private static final String OLD_MD_PATH = "E:\\笔记\\自己整理\\test-md\\old-md-path\\";
private static final String NEW_MD_PATH = "E:\\笔记\\自己整理\\test-md\\new-md-path\\";
private static final String GITEE_PATH_PREFIX = "https://vcoqrkpdhs.oss-cn-shenzhen.aliyuncs.com/img/";

private static final String MD_FILE_EXT = "md";
private static final String PIC_MAP_FILE = "name.txt";

/**
 * 测试复制图片,重命名并复制到一个新路径
 */
@Test
public void test_copy_pic() {
    // 判断字符串是否为纯数字的正则表达式
    Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
    // 列出所有文件
    List<File> loopFiles = FileUtil.loopFiles(OLD_PIC_PATH);
    List<String> nameLines = new ArrayList<>();
    Assert.isTrue(CollUtil.isNotEmpty(loopFiles));
    // 遍历
    for (File lp : loopFiles) {
        // 获取文件的创建日期
        LocalDateTime createdTime = getCreatedTime(lp);
        // 原始文件名
        String fileName = FileNameUtil.getPrefix(lp.getName());
        // 后缀
        String ext = FileNameUtil.getSuffix(lp.getName());
        // 新的文件名
        String newName = fileName;
        // 新路径: 加上创建日期
        String newPath = MID_PIC_PATH + createdTime.format(formatter);
        FileUtil.mkdir(newPath);
        if (pattern.matcher(fileName).matches()) {
            // 文件名是纯数字
            newName = fileName;
        } else {
            // 需要重命名
            if (fileName.startsWith("image-")) {
                newName = StrUtil.replace(fileName, "image-", "");
            } else {
                newName = IdCreator.getId(createdTime);
            }
            nameLines.add(fileName + "." + ext + "," + newName + "." + ext);
        }

        FileUtil.copy(lp, new File(newPath + File.separator + newName + "." + ext), false);
    }
    FileUtil.writeUtf8Lines(nameLines, new File(MID_PIC_PATH + PIC_MAP_FILE));
}

private static LocalDateTime getCreatedTime(File f) {
    try {
        BasicFileAttributes fileAttributes = Files.readAttributes(Paths.get(f.toURI()), BasicFileAttributes.class);
        long millis = fileAttributes.creationTime().toMillis();
        return LocalDateTimeUtil.of(millis);
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException("get time occurs error", e);
    }
}

中间使用到 IdCreator

public class IdCreator {
    private static int addPart = 1;
    private static String result = "";
    private static DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
    private static String lastDate = "";
    private static AtomicInteger atomicInteger = new AtomicInteger();
    private static Map<String, Integer> map = new HashMap<String, Integer>();

    /**
     * 获取主键
     * @param length 长度
     * @return 返回17位时间戳+3位递增数
     */
    public synchronized static String getId(int length) {
        //获取时间部分字符串
        LocalDateTime now = LocalDateTime.now();
        String nowStr = now.format(format);

        //获取数字后缀值部分
        if (IdCreator.lastDate.equals(nowStr)) {
            addPart += 1;
        } else {
            addPart = 1;
            lastDate = nowStr;
        }

        if (length > 17) {
            length -= 17;
            for (int i = 0; i < length - ((addPart + "").length()); i++) {
                nowStr += "0";
            }
            nowStr += addPart;
            result = nowStr;
        } else {
            result = nowStr;
        }

        return result;
    }

    public synchronized static String getId(LocalDateTime date) {
        String nowStr = date.format(format);
        Integer seq = map.get(nowStr);
        if (null == seq) {
            map.put(nowStr, 1);
        }else{
            map.put(nowStr, ++seq);
        }
        return nowStr + map.get(nowStr);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            System.out.println(getId(LocalDateTime.now()));
        }
//        System.out.println(IdCreator.getId(20));
    }
}

2、对文件进行分组,按照日期分组

/**
 * 测试组文件,对文件进行分组,按照日期分组
 */
@Test
public void test_group_file() {
    List<File> loopFiles = FileUtil.loopFiles(MID_PIC_PATH);
    Assert.isTrue(CollUtil.isNotEmpty(loopFiles));
    for (File file : loopFiles) {
        String ext = FileNameUtil.getSuffix(file);
        String name = file.getName();
        if ("txt".equals(ext)) {
            FileUtil.copy(file, new File(NEW_PIC_PATH + File.separator + name), false);
            continue;
        }
        String path = file.getPath();
        String dir = path.replace("\\" + name, "");
        // 20190715 -> 2019/07/15
        String parentDir = dir.substring(dir.lastIndexOf("\\") + 1);
        String newDir = StrUtil.sub(parentDir, 0, 4) + "/" + StrUtil.sub(parentDir, 4, 6) + "/" + StrUtil.sub(parentDir, 6, 8);

        FileUtil.mkdir(NEW_PIC_PATH + newDir);
        FileUtil.copy(file, new File(NEW_PIC_PATH + newDir + "\\" + name), false);
    }
}

3、替换md文件中图片路径


/**
     * 测试改变md url,改变md中的图片地址
     */
@Test
public void test_change_md_url() {
    // 列出所有md文件
    List<File> loopMds = FileUtil.loopFiles(OLD_MD_PATH);
    // 列出所有图片,新文件(已重命名)
    List<File> newPics = FileUtil.loopFiles(NEW_PIC_PATH);
    Assert.isTrue(CollUtil.isNotEmpty(loopMds) && CollUtil.isNotEmpty(newPics));

    // 读取 name.txt 文件,获取映射关系
    List<String> nameMapList = FileUtil.readUtf8Lines(new File(NEW_PIC_PATH + PIC_MAP_FILE));
    // Map<newName, oldName>
    Map<String, String> newOldNameMap = nameMapList.stream().collect(Collectors.toMap(l -> l.split(",")[1], l -> l.split(",")[0]));
    // Map<oldName, newName>
    Map<String, String> oldNewNameMap = nameMapList.stream().collect(Collectors.toMap(l -> l.split(",")[0], l -> l.split(",")[1]));
    // 根据创建日期将图片关系进行映射,Map<fileName, yyyy/MM/dd> -> <image-20200419121824237.png, 2020/04/19>
    Map<String, String> fileDayMap = getNameDayMap(newPics, newOldNameMap);

    for (File md : loopMds) {
        // 获取文件后缀
        String suffix = FileNameUtil.getSuffix(md);
        // 如果不是md文件,不处理
        if (!MD_FILE_EXT.equals(suffix)) {
            continue;
        }
        // 读取md文件的内容
        List<String> lines = FileUtil.readUtf8Lines(md);
        // 替换url,生成新的文件内容
        // ![1568900215270](E:\笔记\自己整理\test-md\old-pic\1568900215270.png) -> ![1568900215270](https://vcoqrkpdhs.oss-cn-shenzhen.aliyuncs.com/img/2021/09/21/1568900215270.png)
        List<String> newLines = getNewLines(fileDayMap, lines, oldNewNameMap, OLD_PIC_PATH, GITEE_PATH_PREFIX);
        // 写入替换后的内容
        FileUtil.writeUtf8Lines(newLines, new File(NEW_MD_PATH + md.getName()));
    }
}

private Map<String, String> getNameDayMap(List<File> newPics, Map<String, String> newOldNameMap) {
    Map<String, String> map = new HashMap<>();
    for (File pic : newPics) {
        String newPicName = pic.getName();
        String newPicPath = pic.getPath();
        // 获取图片所在路径 天,E:\笔记\自己整理\typora-user-images\2020\11\29\202011292139028981.png -> 2020\11\29\
        String day = StrUtil.sub(newPicPath, newPicPath.indexOf(NEW_PIC_PATH) + NEW_PIC_PATH.length(), newPicPath.indexOf(newPicName) - 1);
        // 新文件的路径
        if (newOldNameMap.containsKey(newPicName)) {
            // 获取old名字,如果有重命名的话
            newPicName = newOldNameMap.get(newPicName);
        }
        // <202011292139028981.png, 2020/11/29>
        map.put(newPicName, day.replace("\\", "/"));
    }
    return map;
}

private static List<String> getNewLines(Map<String, String> fileDayMap, List<String> lines, Map<String, String> oldNewNameMap, String oldPrefix, String newPrefix) {
    List<String> newLines = new ArrayList<>(lines.size());

    // 判断是否包含图片
    String picLine = lines.stream().filter(l -> l.contains(oldPrefix)).findFirst().orElse("");
    // 内容不包含图片,不处理
    if (StrUtil.isBlank(picLine)) {
        return lines;
    }

    for (String line : lines) {
        // 是否包含指定前缀
        if (!line.contains(oldPrefix)) {
            newLines.add(line);
            continue;
        }
        // 获取图片名称
        String oldPicName = StrUtil.sub(line, line.indexOf(oldPrefix) + oldPrefix.length(), line.indexOf(")"));
        if (!fileDayMap.containsKey(oldPicName)) {
            newLines.add(line);
            continue;
        }
        // 获取图片创建日期
        String day = fileDayMap.get(oldPicName);
        String newPicName = oldPicName;
        if (oldNewNameMap.containsKey(oldPicName)) {
            newPicName = oldNewNameMap.get(oldPicName);
        }
        // 替换图片地址url
        String newLine = StrUtil.replace(line, oldPrefix + oldPicName, newPrefix + day + "/" + newPicName);
        newLines.add(newLine);
    }
    return newLines;
}

后面需要手动将命名分组后的图片文件复制到本地 Gitee 路径下,然后提交 Gitee,md文件中的图片才能正常显示(这二步也可以考虑使用代码操作)。

完整代码如下:

package com.soulballad.book.mdr;  

import cn.hutool.core.collection.CollUtil;  
import cn.hutool.core.date.LocalDateTimeUtil;  
import cn.hutool.core.io.FileUtil;  
import cn.hutool.core.io.file.FileNameUtil;  
import cn.hutool.core.lang.Assert;  
import cn.hutool.core.util.StrUtil;  
import com.soulballad.book.netty.IdCreator;  
import org.junit.Test;  

import java.io.File;  
import java.io.IOException;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.nio.file.attribute.BasicFileAttributes;  
import java.time.LocalDateTime;  
import java.time.format.DateTimeFormatter;  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.HashMap;  
import java.util.List;  
import java.util.Map;  
import java.util.regex.Matcher;  
import java.util.regex.Pattern;  
import java.util.stream.Collectors;  

/**  
 * @author :soulballad  
 * @version : v1.0 * @since :2021/9/21 21:41  
 */public class MdCopyTest {  
     private static final String OLD_PIC_PATH = "E:\\笔记\\自己整理\\test-md\\old-pic\\";  
     private static final String MID_PIC_PATH = "E:\\笔记\\自己整理\\test-md\\old-pic2\\";  
     private static final String NEW_PIC_PATH = "E:\\笔记\\自己整理\\typora-user-images\\";  

     private static final String OLD_MD_PATH = "E:\\笔记\\Obsidian\\我的知识\\3.笔记\\1-1.Java学习-2\\";  
     private static final String NEW_MD_PATH = "E:\\笔记\\Obsidian\\我的知识\\3.笔记\\1-3.Java学习-4\\";  
     private static final String GITEE_PATH_PREFIX = "https://vcoqrkpdhs.oss-cn-shenzhen.aliyuncs.com/img/";  

     private static final String MD_FILE_EXT = "md";  
     private static final String PIC_MAP_FILE = "name.txt";  
     private static final String MD_PIC_PREFIX = "![";  
     private static final String MD_PIC_LINK_REGEX = ".*!\\[.*]\\(";  


     /**  
 * 测试复制图片,重命名并复制到一个新路径  
 */  
     @Test  
     public void test_copy_pic() {  
         // 判断字符串是否为纯数字的正则表达式  
         Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");  
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");  
         // 列出所有文件  
         List<File> loopFiles = FileUtil.loopFiles(OLD_PIC_PATH);  
         List<String> nameLines = new ArrayList<>();  
         Assert.isTrue(CollUtil.isNotEmpty(loopFiles));  
         // 遍历  
         for (File lp : loopFiles) {  
             // 获取文件的创建日期  
             LocalDateTime createdTime = getCreatedTime(lp);  
             // 原始文件名  
             String fileName = FileNameUtil.getPrefix(lp.getName());  
             // 后缀  
             String ext = FileNameUtil.getSuffix(lp.getName());  
             // 新的文件名  
             String newName = fileName;  
             // 新路径: 加上创建日期  
             String newPath = MID_PIC_PATH + createdTime.format(formatter);  
             FileUtil.mkdir(newPath);  
             if (pattern.matcher(fileName).matches()) {  
                 // 文件名是纯数字  
                 newName = fileName;  
             } else {  
                 // 需要重命名  
                 if (fileName.startsWith("image-")) {  
                     newName = StrUtil.replace(fileName, "image-", "");  
                 } else {  
                     newName = IdCreator.getId(createdTime);  
                 }  
                 nameLines.add(fileName + "." + ext + "," + newName + "." + ext);  
             }  

             FileUtil.copy(lp, new File(newPath + File.separator + newName + "." + ext), false);  
         }  
         FileUtil.writeUtf8Lines(nameLines, new File(MID_PIC_PATH + PIC_MAP_FILE));  
     }  

     /**  
 * 测试组文件,对文件进行分组,按照日期分组  
 */  
     @Test  
     public void test_group_file() {  
         List<File> loopFiles = FileUtil.loopFiles(MID_PIC_PATH);  
         Assert.isTrue(CollUtil.isNotEmpty(loopFiles));  
         for (File file : loopFiles) {  
             String ext = FileNameUtil.getSuffix(file);  
             String name = file.getName();  
             if ("txt".equals(ext)) {  
                 FileUtil.copy(file, new File(NEW_PIC_PATH + File.separator + name), false);  
                 continue;  
             }  
             String path = file.getPath();  
             String dir = path.replace("\\" + name, "");  
             // 20190715 -> 2019/07/15  
             String parentDir = dir.substring(dir.lastIndexOf("\\") + 1);  
             String newDir = StrUtil.sub(parentDir, 0, 4) + "/" + StrUtil.sub(parentDir, 4, 6) + "/" + StrUtil.sub(parentDir, 6, 8);  

             FileUtil.mkdir(NEW_PIC_PATH + newDir);  
             FileUtil.copy(file, new File(NEW_PIC_PATH + newDir + "\\" + name), false);  
         }  
     }  

     /**  
 * 测试改变md url,改变md中的图片地址  
 */  
     @Test  
     public void test_change_md_url() {  
         // 列出所有md文件  
         List<File> loopMds = FileUtil.loopFiles(OLD_MD_PATH);  
         // 列出所有图片,新文件(已重命名)  
         List<File> newPics = FileUtil.loopFiles(NEW_PIC_PATH);  
         Assert.isTrue(CollUtil.isNotEmpty(loopMds) && CollUtil.isNotEmpty(newPics));  

         // 读取 name.txt 文件,获取映射关系  
         List<String> nameMapList = FileUtil.readUtf8Lines(new File(NEW_PIC_PATH + PIC_MAP_FILE));  
         // Map<newName, oldName>  
         Map<String, String> newOldNameMap = nameMapList.stream().collect(Collectors.toMap(l -> l.split(",")[1], l -> l.split(",")[0]));  
         // Map<oldName, newName>  
         Map<String, String> oldNewNameMap = nameMapList.stream().collect(Collectors.toMap(l -> l.split(",")[0], l -> l.split(",")[1]));  
         // 根据创建日期将图片关系进行映射,Map<fileName, yyyy/MM/dd> -> <image-20200419121824237.png, 2020/04/19>  
         Map<String, String> fileDayMap = getNameDayMap(newPics, newOldNameMap);  

         for (File md : loopMds) {  
             // 获取文件后缀  
             String suffix = FileNameUtil.getSuffix(md);  
             // 如果不是md文件,不处理  
             if (!MD_FILE_EXT.equals(suffix)) {  
                 continue;  
             }  
             // 读取md文件的内容  
             List<String> lines = FileUtil.readUtf8Lines(md);  
             // 替换url,生成新的文件内容  
             // ![1568900215270](E:\笔记\自己整理\test-md\old-pic\1568900215270.png) -> ![1568900215270](https://vcoqrkpdhs.oss-cn-shenzhen.aliyuncs.com/img/2021/09/21/1568900215270.png)  
             List<String> newLines = getNewLines(fileDayMap, lines, oldNewNameMap, MD_PIC_PREFIX, GITEE_PATH_PREFIX);  
             // 写入替换后的内容  
             String sepPath = StrUtil.replace(md.getPath(), OLD_MD_PATH, "");  
             FileUtil.writeUtf8Lines(newLines, new File(NEW_MD_PATH + sepPath));  
         }  
     }  

     /**  
 * 根据实际文件地址改变图片地址  
 */  
     @Test  
     public void test_change_md_url2() {  
         // 列出所有md文件  
         List<File> loopMds = FileUtil.loopFiles(OLD_MD_PATH);  
         // 列出所有图片,新文件(已重命名)  
         List<File> newPics = FileUtil.loopFiles(NEW_PIC_PATH);  
         Assert.isTrue(CollUtil.isNotEmpty(loopMds) && CollUtil.isNotEmpty(newPics));  

         // 根据实际文件路径,获取映射关系  
         Map<String, String> picPathMap = newPics.stream().collect(Collectors.toMap(File::getName, f -> f.getPath().replace(NEW_PIC_PATH, "").replaceAll("\\\\", "/"), (k, v) -> v));  

         for (File md : loopMds) {  
             // 获取文件后缀  
             String suffix = FileNameUtil.getSuffix(md);  
             // 如果不是md文件,不处理  
             if (!MD_FILE_EXT.equals(suffix)) {  
                 continue;  
             }  
             // 读取md文件的内容  
             List<String> lines = FileUtil.readUtf8Lines(md);  
             // 替换url,生成新的文件内容  
             // ![1568900215270](E:\笔记\自己整理\test-md\old-pic\1568900215270.png) -> ![1568900215270](https://vcoqrkpdhs.oss-cn-shenzhen.aliyuncs.com/img/2021/09/21/1568900215270.png)  
             List<String> newLines = getNewLines(picPathMap, lines, Collections.emptyMap(), MD_PIC_PREFIX, GITEE_PATH_PREFIX);  
             // 写入替换后的内容  
             String sepPath = StrUtil.replace(md.getPath(), OLD_MD_PATH, "");  
             FileUtil.writeUtf8Lines(newLines, new File(NEW_MD_PATH + sepPath));  
         }  
     }  

     private Map<String, String> getNameDayMap(List<File> newPics, Map<String, String> newOldNameMap) {  
         Map<String, String> map = new HashMap<>();  
         for (File pic : newPics) {  
             String newPicName = pic.getName();  
             String newPicPath = pic.getPath();  
             // 获取图片所在路径 天,E:\笔记\自己整理\typora-user-images\2020\11\29\202011292139028981.png -> 2020\11\29\  
             String day = StrUtil.sub(newPicPath, newPicPath.indexOf(NEW_PIC_PATH) + NEW_PIC_PATH.length(), newPicPath.indexOf(newPicName) - 1);  
             // 新文件的路径  
             if (newOldNameMap.containsKey(newPicName)) {  
                 // 获取old名字,如果有重命名的话  
                 newPicName = newOldNameMap.get(newPicName);  
             }  
             // <202011292139028981.png, 2020/11/29>  
             map.put(newPicName, day.replace("\\", "/"));  
         }  
         return map;  
     }  

     private static List<String> getNewLines(Map<String, String> fileDayMap, List<String> lines, Map<String, String> oldNewNameMap, String oldPrefix, String newPrefix) {  
         List<String> newLines = new ArrayList<>(lines.size());  

         // 判断是否包含图片  
         String picLine = lines.stream().filter(l -> l.contains(oldPrefix)).findFirst().orElse("");  
         // 内容不包含图片,不处理  
         if (StrUtil.isBlank(picLine)) {  
             return lines;  
         }  

         Pattern pattern = Pattern.compile(MD_PIC_LINK_REGEX);  
         for (String line : lines) {  
             // 是否包含指定前缀  
             if (!line.contains(oldPrefix)) {  
                 newLines.add(line);  
                 continue;  
             }  
             // 获取图片名称  
             // String oldPicName = StrUtil.sub(line, line.indexOf(oldPrefix) + oldPrefix.length(), line.indexOf(")"));  
             String oldPicName = StrUtil.sub(line, line.lastIndexOf("/") + 1, line.indexOf(")"));  
             if (!fileDayMap.containsKey(oldPicName)) {  
                 newLines.add(line);  
                 continue;  
             }  
             // 获取图片创建日期  
             String day = fileDayMap.get(oldPicName);  
             String newPicName = oldPicName;  
             if (oldNewNameMap.containsKey(oldPicName)) {  
                 newPicName = oldNewNameMap.get(oldPicName);  
             }  
             // 替换图片地址url  
             Matcher matcher = pattern.matcher(line);  
             if (!matcher.find()) {  
                 System.err.println(line);  
                 continue;  
             }  

             String newLine = matcher.group() + newPrefix + day + ")";  
             //String newLine = StrUtil.replace(line, oldPrefix + oldPicName, newPrefix + day + "/" + newPicName);  
             newLines.add(newLine);  
         }  
         return newLines;  
     }  

     private static LocalDateTime getCreatedTime(File f) {  
         try {  
             BasicFileAttributes fileAttributes = Files.readAttributes(Paths.get(f.toURI()), BasicFileAttributes.class);  
             long millis = fileAttributes.creationTime().toMillis();  
             return LocalDateTimeUtil.of(millis);  
         } catch (IOException e) {  
             e.printStackTrace();  
             throw new RuntimeException("get time occurs error", e);  
         }  
     }  
 }

上次编辑于:
贡献者: soulballad