最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實(shí)現(xiàn)Markdown圖片批量本地化處理工具

 更新時(shí)間:2025年07月30日 09:44:13   作者:zk_xyb  
在日常使用Markdown時(shí),我們常通過遠(yuǎn)程URL引用圖片,所以本文小編就為大家詳細(xì)介紹一下如何使用Java實(shí)現(xiàn)Markdown圖片批量本地化處理吧

一、工具介紹

在日常使用Markdown時(shí),我們常通過遠(yuǎn)程URL引用圖片,但這種方式存在依賴網(wǎng)絡(luò)、圖片易失效、離線無法查看等問題。MarkdownImageProcessor 正是為解決這些問題而生的Java工具,其核心功能是批量識(shí)別Markdown文件中的遠(yuǎn)程圖片URL,自動(dòng)下載圖片到本地目錄,并生成替換后的新Markdown文件(圖片路徑改為本地相對(duì)路徑),讓文檔徹底擺脫對(duì)遠(yuǎn)程資源的依賴。

核心功能

多文件/目錄支持:可處理單個(gè)Markdown文件或目錄(遞歸查找所有.md/.markdown文件);

遠(yuǎn)程圖片下載:自動(dòng)識(shí)別http:///https://開頭的遠(yuǎn)程圖片URL,下載至本地images目錄;

路徑自動(dòng)替換:生成的新Markdown文件中,圖片路徑會(huì)替換為本地相對(duì)路徑(如images/xxx.jpg);

安全處理機(jī)制:下載失敗時(shí)保留原始URL,避免文檔損壞;文件名自動(dòng)去重(同名文件加序號(hào)),防止覆蓋;

清晰的進(jìn)度反饋:實(shí)時(shí)輸出處理進(jìn)度(成功/失敗的文件/圖片數(shù)量),生成處理總結(jié)。

使用流程

運(yùn)行程序后,輸入Markdown文件路徑或目錄路徑(每行一個(gè));

輸入空行開始處理,程序會(huì)遞歸掃描目錄中的所有Markdown文件;

處理完成后,在原文件同目錄生成帶_processed后綴的新文件(如doc.mddoc_processed.md),圖片保存至同目錄的images文件夾。

二、代碼優(yōu)化方案

原代碼功能完整,但在靈活性、健壯性和現(xiàn)代Java特性使用上有優(yōu)化空間。以下是優(yōu)化后的代碼及關(guān)鍵改進(jìn)點(diǎn):

優(yōu)化后的代碼

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class MarkdownImageProcessor {
    // 可配置參數(shù)(默認(rèn)值)
    private final String imageRegex;
    private String imageDir;
    private String processedSuffix;
    private int connectionTimeout; // 連接超時(shí)(毫秒)
    private int readTimeout; // 讀取超時(shí)(毫秒)
    private String userAgent;

    private static final Scanner scanner = new Scanner(System.in);

    // 構(gòu)造函數(shù):支持自定義配置
    public MarkdownImageProcessor() {
        this.imageRegex = "!\\[(.*?)\\]\\((.*?)\\)";
        this.imageDir = "images";
        this.processedSuffix = "_processed";
        this.connectionTimeout = 5000; // 5秒
        this.readTimeout = 10000; // 10秒
        this.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
    }

    public static void main(String[] args) {
        try {
            MarkdownImageProcessor processor = new MarkdownImageProcessor();
            List<Path> filesToProcess = processor.collectFilesFromConsole();
            
            if (filesToProcess.isEmpty()) {
                System.out.println("沒有需要處理的文件。");
                return;
            }
            
            ProcessSummary summary = processor.processFiles(filesToProcess);
            processor.printSummary(summary);
            
        } catch (Exception e) {
            System.err.println("程序運(yùn)行出錯(cuò): " + e.getMessage());
            e.printStackTrace();
        } finally {
            scanner.close();
        }
    }

    // 從控制臺(tái)收集待處理文件
    private List<Path> collectFilesFromConsole() {
        List<Path> files = new ArrayList<>();
        System.out.println("Markdown圖片處理工具 - 批量模式");
        System.out.println("--------------------------------");
        System.out.println("請(qǐng)輸入Markdown文件或目錄路徑(每行一個(gè),輸入空行開始處理)");
        System.out.println("支持:");
        System.out.println("  - 單個(gè)文件路徑");
        System.out.println("  - 目錄路徑(將遞歸處理所有.md文件)");
        System.out.println("--------------------------------");
        
        Set<String> processedPaths = new HashSet<>();
        
        while (true) {
            System.out.print("輸入路徑: ");
            String pathStr = scanner.nextLine().trim();
            
            if (pathStr.isEmpty()) {
                break;
            }
            
            if (processedPaths.contains(pathStr)) {
                System.out.println("警告: 路徑已添加 - " + pathStr);
                continue;
            }
            
            Path path = Paths.get(pathStr);
            if (!Files.exists(path)) {
                System.out.println("錯(cuò)誤: 文件/目錄不存在 - " + pathStr);
                continue;
            }
            
            processedPaths.add(pathStr);
            
            if (Files.isDirectory(path)) {
                List<Path> dirFiles = getMdFilesRecursively(path);
                files.addAll(dirFiles);
                System.out.printf("已添加目錄: %s (%d個(gè)MD文件)%n", path, dirFiles.size());
            } else {
                if (isMarkdownFile(path)) {
                    files.add(path);
                    System.out.println("已添加文件: " + path);
                } else {
                    System.out.println("錯(cuò)誤: 不是Markdown文件 - " + pathStr);
                }
            }
        }
        
        return files;
    }

    // 遞歸獲取目錄中所有Markdown文件(使用NIO簡(jiǎn)化代碼)
    private List<Path> getMdFilesRecursively(Path directory) {
        List<Path> result = new ArrayList<>();
        try (Stream<Path> stream = Files.walk(directory)) {
            stream.filter(Files::isRegularFile)
                  .filter(this::isMarkdownFile)
                  .forEach(result::add);
        } catch (IOException e) {
            System.err.printf("警告: 讀取目錄失敗 %s - %s%n", directory, e.getMessage());
        }
        return result;
    }

    // 判斷是否為Markdown文件
    private boolean isMarkdownFile(Path path) {
        String fileName = path.getFileName().toString().toLowerCase();
        return fileName.endsWith(".md") || fileName.endsWith(".markdown");
    }

    // 處理所有文件
    private ProcessSummary processFiles(List<Path> files) {
        ProcessSummary summary = new ProcessSummary();
        int totalFiles = files.size();
        
        System.out.printf("%n開始處理 %d 個(gè)Markdown文件...%n%n", totalFiles);
        
        for (int i = 0; i < totalFiles; i++) {
            Path file = files.get(i);
            System.out.printf("處理文件 %d/%d: %s%n", i + 1, totalFiles, file.toAbsolutePath());
            
            try {
                processMarkdownFile(file);
                summary.successfulFiles++;
            } catch (Exception e) {
                System.err.printf("  處理失敗: %s%n", e.getMessage());
                summary.failedFiles++;
            }
            
            System.out.println();
        }
        
        return summary;
    }

    // 處理單個(gè)Markdown文件
    private void processMarkdownFile(Path mdFile) throws IOException {
        // 讀取文件內(nèi)容
        String content = readFileContent(mdFile);

        // 創(chuàng)建圖片保存目錄(基于原文件目錄)
        Path imageDirPath = mdFile.getParent().resolve(imageDir);
        Files.createDirectories(imageDirPath); // 自動(dòng)創(chuàng)建父目錄

        // 處理圖片URL并下載
        ImageProcessingResult result = processImages(content, imageDirPath);
        
        if (result.totalImages > 0) {
            // 生成新的MD文件路徑
            Path newMdFile = getProcessedFilePath(mdFile);
            writeFileContent(newMdFile, result.processedContent);
            
            System.out.printf("  已處理 %d 張圖片,生成新文件: %s%n", 
                              result.totalImages, newMdFile.getFileName());
        } else {
            System.out.println("  未發(fā)現(xiàn)需要處理的遠(yuǎn)程圖片URL");
        }
    }

    // 使用Java 8兼容的方式讀取文件內(nèi)容
    private String readFileContent(Path path) throws IOException {
        StringBuilder content = new StringBuilder();
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
        }
        return content.toString();
    }

    // 使用Java 8兼容的方式寫入文件內(nèi)容
    private void writeFileContent(Path path, String content) throws IOException {
        try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
            writer.write(content);
        }
    }

    // 獲取處理后的文件路徑(可自定義后綴)
    private Path getProcessedFilePath(Path originalPath) {
        String fileName = originalPath.getFileName().toString();
        int dotIndex = fileName.lastIndexOf('.');
        String baseName = (dotIndex > 0) ? fileName.substring(0, dotIndex) : fileName;
        String extension = (dotIndex > 0) ? fileName.substring(dotIndex) : "";
        String newFileName = baseName + processedSuffix + extension;
        return originalPath.getParent().resolve(newFileName);
    }

    // 處理圖片URL并替換為本地路徑
    private ImageProcessingResult processImages(String content, Path imageDirPath) throws IOException {
        Pattern pattern = Pattern.compile(imageRegex);
        Matcher matcher = pattern.matcher(content);
        StringBuffer sb = new StringBuffer();
        int totalImages = 0;
        int failedImages = 0;

        while (matcher.find()) {
            String altText = matcher.group(1);
            String imageUrl = matcher.group(2).trim();

            // 處理遠(yuǎn)程圖片URL(http/https)
            if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
                totalImages++;
                try {
                    // 下載圖片并返回本地文件名
                    String fileName = downloadImage(imageUrl, imageDirPath);
                    // 構(gòu)建相對(duì)路徑(原文件到images目錄的相對(duì)路徑)
                    Path relativePath = imageDirPath.getParent().relativize(imageDirPath).resolve(fileName);
                    String replacement = "![" + altText + "](" + relativePath + ")";
                    matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
                    System.out.printf("    ? 已下載: %s -> %s%n", imageUrl, relativePath);
                } catch (Exception e) {
                    failedImages++;
                    System.err.printf("    ? 下載失敗 (%s): %s%n", e.getMessage(), imageUrl);
                    // 保留原始URL
                    matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0)));
                }
            } else {
                // 本地圖片路徑不處理
                matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0)));
            }
        }
        matcher.appendTail(sb);

        ImageProcessingResult result = new ImageProcessingResult();
        result.processedContent = sb.toString();
        result.totalImages = totalImages;
        result.successfulImages = totalImages - failedImages;
        result.failedImages = failedImages;
        
        return result;
    }

    // 下載圖片并保存到本地目錄
    private String downloadImage(String imageUrl, Path imageDirPath) throws IOException {
        // 創(chuàng)建HTTP連接并設(shè)置超時(shí)
        HttpURLConnection connection = createHttpConnection(imageUrl);
        
        // 獲取響應(yīng)狀態(tài)
        int responseCode = connection.getResponseCode();
        if (responseCode != HttpURLConnection.HTTP_OK) {
            throw new IOException("HTTP請(qǐng)求失敗,狀態(tài)碼: " + responseCode);
        }

        // 根據(jù)Content-Type獲取正確的文件擴(kuò)展名
        String contentType = connection.getContentType();
        String extension = getExtensionFromContentType(contentType);

        // 提取基礎(chǔ)文件名(不含擴(kuò)展名)
        String baseFileName = extractBaseFileName(imageUrl);
        // 拼接完整文件名(基礎(chǔ)名+擴(kuò)展名)
        String originalFileName = baseFileName + "." + extension;
        // 生成唯一文件名(避免重復(fù))
        String fileName = generateUniqueFileName(imageDirPath, originalFileName);

        // 下載并保存文件
        try (InputStream in = connection.getInputStream();
             OutputStream out = Files.newOutputStream(imageDirPath.resolve(fileName))) {
            
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
        
        return fileName;
    }

    // 創(chuàng)建HTTP連接(統(tǒng)一配置超時(shí)和請(qǐng)求頭)
    private HttpURLConnection createHttpConnection(String urlStr) throws IOException {
        URL url = new URL(urlStr);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("User-Agent", userAgent);
        connection.setConnectTimeout(connectionTimeout); // 連接超時(shí)
        connection.setReadTimeout(readTimeout); // 讀取超時(shí)
        connection.setInstanceFollowRedirects(true); // 自動(dòng)跟隨重定向
        return connection;
    }

    // 從Content-Type獲取正確的文件擴(kuò)展名(兼容Java 8)
    private String getExtensionFromContentType(String contentType) {
        if (contentType == null) {
            return "jpg"; // 默認(rèn) fallback
        }
        // 傳統(tǒng)switch語句(Java 8支持)
        switch (contentType) {
            case "image/jpeg":
                return "jpg";
            case "image/png":
                return "png";
            case "image/gif":
                return "gif";
            case "image/bmp":
                return "bmp";
            case "image/webp":
                return "webp";
            default:
                return "jpg"; // 未知類型默認(rèn)jpg
        }
    }

    // 提取基礎(chǔ)文件名(不含擴(kuò)展名)
    private String extractBaseFileName(String url) {
        // 移除URL查詢參數(shù)和錨點(diǎn)
        String path = url.split("[?#]")[0];
        // 提取最后一個(gè)路徑段
        String fileName = path.substring(path.lastIndexOf('/') + 1);
        // 移除可能的擴(kuò)展名(避免重復(fù))
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex > 0) {
            fileName = fileName.substring(0, dotIndex);
        }
        //  sanitize文件名(移除非法字符)
        return fileName.replaceAll("[\\\\/:*?\"<>|]", "_");
    }

    // 生成唯一文件名(避免覆蓋)
    private String generateUniqueFileName(Path directory, String originalName) {
        String baseName = originalName;
        String extension = "";
        int dotIndex = originalName.lastIndexOf('.');
        if (dotIndex > 0) {
            baseName = originalName.substring(0, dotIndex);
            extension = originalName.substring(dotIndex);
        }

        String fileName = originalName;
        int counter = 1;
        while (Files.exists(directory.resolve(fileName))) {
            fileName = baseName + "_" + counter + extension;
            counter++;
        }

        return fileName;
    }

    // 打印處理總結(jié)
    private void printSummary(ProcessSummary summary) {
        System.out.println("--------------------------------");
        System.out.println("處理完成!");
        System.out.printf("成功處理文件: %d%n", summary.successfulFiles);
        System.out.printf("處理失敗文件: %d%n", summary.failedFiles);
        System.out.println("--------------------------------");
    }

    // 配置參數(shù)設(shè)置方法(提高靈活性)
    public void setImageDir(String imageDir) {
        this.imageDir = imageDir;
    }

    public void setProcessedSuffix(String processedSuffix) {
        this.processedSuffix = processedSuffix;
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }

    // 處理總結(jié)內(nèi)部類
    static class ProcessSummary {
        int successfulFiles = 0;
        int failedFiles = 0;
    }

    // 圖片處理結(jié)果內(nèi)部類
    static class ImageProcessingResult {
        String processedContent;
        int totalImages;
        int successfulImages;
        int failedImages;
    }
}

主要優(yōu)化點(diǎn)

增強(qiáng)靈活性:將images目錄、輸出文件后綴(_processed)、HTTP超時(shí)等硬編碼參數(shù)改為可配置字段(通過setter調(diào)整),適應(yīng)不同場(chǎng)景。

現(xiàn)代IO API:用java.nio.filePath、Files)替代File類,簡(jiǎn)化路徑處理(如resolve拼接路徑、createDirectories自動(dòng)創(chuàng)建目錄),代碼更簡(jiǎn)潔。

準(zhǔn)確的文件擴(kuò)展名:原代碼默認(rèn)用jpg,優(yōu)化后通過Content-Type動(dòng)態(tài)獲?。ㄈ?code>image/png對(duì)應(yīng)png),避免文件格式錯(cuò)誤。

HTTP超時(shí)控制:為HttpURLConnection設(shè)置連接超時(shí)和讀取超時(shí),防止因網(wǎng)絡(luò)問題導(dǎo)致程序卡死。

遞歸查找優(yōu)化:用Files.walk替代手動(dòng)遞歸,代碼更簡(jiǎn)潔,且自動(dòng)處理異常。

路徑處理優(yōu)化:生成新文件路徑時(shí)通過Path API操作,避免跨平臺(tái)路徑分隔符問題(如Windows的\和Linux的/)。

代碼復(fù)用:抽離createHttpConnectiongetExtensionFromContentType等方法,減少重復(fù)代碼,提高可維護(hù)性。

錯(cuò)誤處理增強(qiáng):細(xì)化異常信息(如區(qū)分連接超時(shí)、HTTP錯(cuò)誤碼),用戶可更清晰定位問題。

通過這些優(yōu)化,工具不僅保留了原有的核心功能,還在靈活性、健壯性和易用性上有顯著提升,更適合實(shí)際場(chǎng)景使用。

到此這篇關(guān)于Java實(shí)現(xiàn)Markdown圖片批量本地化處理工具的文章就介紹到這了,更多相關(guān)Java圖片批處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 關(guān)于@JsonProperty,@NotNull,@JsonIgnore的具體使用

    關(guān)于@JsonProperty,@NotNull,@JsonIgnore的具體使用

    這篇文章主要介紹了關(guān)于@JsonProperty,@NotNull,@JsonIgnore的具體使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 淺談Java中方法參數(shù)傳遞的問題

    淺談Java中方法參數(shù)傳遞的問題

    下面小編就為大家?guī)硪黄獪\談Java中方法參數(shù)傳遞的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-08-08
  • 解決SpringCloud Gateway配置自定義路由404的坑

    解決SpringCloud Gateway配置自定義路由404的坑

    這篇文章主要介紹了解決SpringCloud Gateway配置自定義路由404的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 一文詳解Springboot中filter的原理與注冊(cè)

    一文詳解Springboot中filter的原理與注冊(cè)

    這篇文章主要為大家詳細(xì)介紹了Springboot中filter的原理與注冊(cè)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),對(duì)我們掌握SpringBoot有一定的幫助,需要的可以參考一下
    2023-02-02
  • 淺談SpringCloud feign的http請(qǐng)求組件優(yōu)化方案

    淺談SpringCloud feign的http請(qǐng)求組件優(yōu)化方案

    這篇文章主要介紹了淺談SpringCloud feign的http請(qǐng)求組件優(yōu)化方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • Spring框架 引入@Resource注解報(bào)空指針的解決

    Spring框架 引入@Resource注解報(bào)空指針的解決

    這篇文章主要介紹了Spring框架 引入@Resource注解報(bào)空指針的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • IDEA中Java出現(xiàn)無效的源發(fā)行版錯(cuò)誤的解決辦法

    IDEA中Java出現(xiàn)無效的源發(fā)行版錯(cuò)誤的解決辦法

    這篇文章主要給大家介紹了關(guān)于IDEA中Java出現(xiàn)無效的源發(fā)行版錯(cuò)誤的解決辦法,IDEA中Java出現(xiàn)?效的源發(fā)?版解決辦法出現(xiàn)該問題的原因是項(xiàng)?Project當(dāng)中的jdk與電腦當(dāng)中的jdk版本不?致造成的,需要的朋友可以參考下
    2023-10-10
  • Java常用類之System類的使用指南

    Java常用類之System類的使用指南

    System類代表系統(tǒng),系統(tǒng)級(jí)的很多屬性和控制方法都放置在該類的內(nèi)部。該類位于java.lang包。本文將通過示例為大家詳細(xì)講講System類的使用,需要的可以參考一下
    2022-07-07
  • SpringBoot實(shí)現(xiàn)消息推送的實(shí)戰(zhàn)(讓服務(wù)器學(xué)會(huì)"主動(dòng)搭訕")

    SpringBoot實(shí)現(xiàn)消息推送的實(shí)戰(zhàn)(讓服務(wù)器學(xué)會(huì)"主動(dòng)搭訕")

    本文介紹了如何使用SSE技術(shù)實(shí)現(xiàn)SpringBoot應(yīng)用的消息推送,并提供了詳細(xì)的實(shí)戰(zhàn)步驟和優(yōu)化建議,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2026-03-03
  • SpringCloud中的Feign遠(yuǎn)程調(diào)用最佳實(shí)踐方案

    SpringCloud中的Feign遠(yuǎn)程調(diào)用最佳實(shí)踐方案

    本文介紹了Feign作為聲明式HTTP客戶端的優(yōu)勢(shì),包括提升代碼可讀性、簡(jiǎn)化URL維護(hù)及支持自定義配置(日志級(jí)別、連接池),通過模塊化抽取Feign客戶端和公共類,實(shí)現(xiàn)代碼復(fù)用并解決掃描包問題,優(yōu)化微服務(wù)間調(diào)用效率,對(duì)SpringCloud Feign遠(yuǎn)程調(diào)用相關(guān)知識(shí)感興趣的朋友一起看看吧
    2025-07-07

最新評(píng)論

闵行区| 温州市| 商都县| 通海县| 永泰县| 黎平县| 互助| 太康县| 前郭尔| 林西县| 葫芦岛市| 新源县| 南昌市| 建阳市| 黑龙江省| 黄浦区| 航空| 镇安县| 米脂县| 左云县| 连云港市| 潮安县| 新丰县| 竹山县| 财经| 揭东县| 永年县| 许昌县| 土默特右旗| 邵阳市| 呼图壁县| 彭阳县| 四平市| 永胜县| 济宁市| 筠连县| 马鞍山市| 静海县| 邹城市| 察隅县| 金湖县|