Java:Tomcat 9, flexmark 0.6 和 mermaid.min.js 10.9 实现 Markdown 中 Mermaid 图表的渲染

Java 如何用 tomcat 9.0 , flexmark 0.6 和 mermaid.min.js 10.9 版本来实现 Markdown 中 Mermaid 图表的渲染,请提供一个完整可运行的示例方案。逻辑参考上一篇 server.js

完整实现方案:Tomcat 9 + Flexmark 0.6 + Mermaid 10.9 渲染 Markdown 中的 Mermaid 图表

一、技术栈说明
组件 版本要求 核心作用
Tomcat 9.0.x Java Web 服务器,部署 Web 应用
Flexmark 0.64.8(兼容0.6+) Markdown 解析渲染核心库
Flexmark-ext-mermaid 0.64.8 Mermaid 语法扩展插件
Mermaid.js 10.9.0 前端渲染 Mermaid 图表(流程图/关系图等)
JDK 8+ 运行 Java 应用
二、项目结构(Maven Web 工程)
复制代码
MermaidMarkdownWeb/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── mermaid/
│   │   │           ├── controller/
│   │   │           │   └── MarkdownController.java  // 核心接口控制器
│   │   │           ├── util/
│   │   │           │   └── CsvToMermaidUtil.java    // TXT→Mermaid转换工具(参考server.js逻辑)
│   │   │           └── config/
│   │   │               └── FlexmarkConfig.java      // Flexmark配置类
│   │   ├── resources/
│   │   └── webapp/
│   │       ├── index.html                          // 前端上传页面
│   │       ├── WEB-INF/
│   │       │   ├── web.xml                         // Web配置(可选,用注解可省略)
│   │       │   └── lib/                            // 若不使用Maven,手动导入JAR包
│   │       └── static/
│   │           └── js/
│   │               └── mermaid.min.js             // 10.9版本Mermaid前端库
│   └── test/
└── pom.xml                                          // Maven依赖配置
三、核心配置与代码实现
1. Maven 依赖(pom.xml)
xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mermaid</groupId>
    <artifactId>MermaidMarkdownWeb</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>Mermaid Markdown Render WebApp</name>

    <!-- 依赖版本管理 -->
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <flexmark.version>0.64.8</flexmark.version>
        <servlet-api.version>4.0.1</servlet-api.version>
        <commons-fileupload.version>1.4</commons-fileupload.version>
    </properties>

    <dependencies>
        <!-- Servlet API(Tomcat 9兼容) -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet-api.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- Flexmark 核心 + Mermaid扩展 -->
        <dependency>
            <groupId>com.vladsch.flexmark</groupId>
            <artifactId>flexmark-all</artifactId>
            <version>${flexmark.version}</version>
        </dependency>
        <dependency>
            <groupId>com.vladsch.flexmark</groupId>
            <artifactId>flexmark-ext-mermaid</artifactId>
            <version>${flexmark.version}</version>
        </dependency>

        <!-- 文件上传依赖 -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>${commons-fileupload.version}</version>
        </dependency>

        <!-- 日志(可选) -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>
    </dependencies>

    <!-- 打包配置 -->
    <build>
        <finalName>MermaidMarkdownWeb</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
2. Flexmark 配置类(FlexmarkConfig.java)

核心作用:配置 Markdown 解析规则,启用 Mermaid 扩展(参考 server.js 中 markdown-it 的 mermaid 插件逻辑)

java 复制代码
package com.mermaid.config;

import com.vladsch.flexmark.ext.mermaid.MermaidExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.data.MutableDataSet;

public class FlexmarkConfig {
    // 初始化 Markdown 解析器和 HTML 渲染器
    public static Parser getParser() {
        MutableDataSet options = new MutableDataSet();
        // 启用基础功能(类似 server.js 的 html: true, linkify: true)
        options.set(Parser.ENABLE_HTML_BLOCKS, true);
        options.set(Parser.ENABLE_HTML_INLINE, true);
        options.set(Parser.LINKIFY, true);
        // 启用 Mermaid 扩展(关键:支持 ```mermaid 代码块)
        options.set(Parser.EXTENSIONS, java.util.Collections.singletonList(MermaidExtension.create()));
        return Parser.builder(options).build();
    }

    public static HtmlRenderer getHtmlRenderer() {
        MutableDataSet options = new MutableDataSet();
        // 配置 Mermaid 渲染(生成 <div class="mermaid"> 标签,与 server.js 一致)
        options.set(HtmlRenderer.EXTENSIONS, java.util.Collections.singletonList(MermaidExtension.create()));
        return HtmlRenderer.builder(options).build();
    }
}
3. TXT→Mermaid 转换工具(CsvToMermaidUtil.java)

完全参考 server.js 的 validateAndConvertCsvToMermaid 逻辑,实现 TXT(CSV格式)到 Mermaid 代码块的转换

java 复制代码
package com.mermaid.util;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class CsvToMermaidUtil {
    /**
     * 校验并转换 TXT(CSV格式)为 Mermaid 代码块
     * @param fileName 文件名
     * @param content 文件内容
     * @return 转换结果(valid:是否成功,content:转换后内容/错误信息)
     */
    public static Result validateAndConvert(String fileName, String content) {
        // 统一换行符
        String normalizedContent = content.replace("\r\n", "\n").replace("\r", "\n");
        // 拆分并过滤空行
        String[] linesArr = normalizedContent.split("\n");
        List<String> lines = new ArrayList<>();
        for (String line : linesArr) {
            if (!line.trim().isEmpty()) {
                lines.add(line.trim());
            }
        }

        // 校验空文件
        if (lines.isEmpty()) {
            return new Result(false, ".txt 文本文件格式错");
        }

        // 校验首行格式(必须是 source,target,weight)
        String firstLine = lines.get(0);
        if (!"source,target,weight".equals(firstLine)) {
            return new Result(false, ".txt 文本文件格式错");
        }

        // 生成 Mermaid 横向流程图(graph LR,与 server.js 一致)
        StringBuilder mermaidContent = new StringBuilder("graph LR\n");
        // 处理数据行(跳过首行)
        for (int i = 1; i < lines.size(); i++) {
            String line = lines.get(i);
            String[] parts = line.split(",");
            // 跳过格式不完整的行
            if (parts.length < 2) continue;

            String source = parts[0].trim();
            String target = parts[1].trim();
            String weight = parts.length >= 3 ? parts[2].trim() : "";

            // 避免节点名冲突,添加前缀(与 server.js 一致)
            String sourceNode = "node_" + source;
            String targetNode = "node_" + target;
            // 拼接权重标签(有则显示,无则省略)
            String weightLabel = weight.isEmpty() ? "" : "|" + weight + "|";

            // 拼接 Mermaid 语法:node_张三["张三"] -->|10000| node_李四["李四"]
            mermaidContent.append("  ").append(sourceNode)
                    .append("[\"").append(source).append("\"] -->")
                    .append(weightLabel).append(" ")
                    .append(targetNode).append("[\"").append(target).append("\"]\n");
        }

        // 包装为 Markdown 代码块(含标题和转换时间)
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        String finalMdContent = "# " + fileName + " 转换为 Md关系图\n\n"
                + "```mermaid\n"
                + mermaidContent.toString()
                + "```\n\n"
                + "> 转换时间:" + dateStr;

        return new Result(true, finalMdContent);
    }

    // 转换结果封装类
    public static class Result {
        private boolean valid;
        private String content;

        public Result(boolean valid, String content) {
            this.valid = valid;
            this.content = content;
        }

        // getter/setter
        public boolean isValid() { return valid; }
        public String getContent() { return content; }
    }
}
4. 核心控制器(MarkdownController.java)

处理文件上传、格式校验、转换、Markdown渲染,对应 server.js 的 //upload-md 接口

java 复制代码
package com.mermaid.controller;

import com.mermaid.config.FlexmarkConfig;
import com.mermaid.util.CsvToMermaidUtil;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@WebServlet(urlPatterns = {"/", "/upload-md"}) // 映射根路径和上传接口
public class MarkdownController extends HttpServlet {
    // Flexmark 解析器和渲染器(全局单例,避免重复初始化)
    private static final Parser MARKDOWN_PARSER = FlexmarkConfig.getParser();
    private static final HtmlRenderer HTML_RENDERER = FlexmarkConfig.getHtmlRenderer();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 根路径请求:返回前端上传页面(对应 server.js 的 app.get('/'))
        req.getRequestDispatcher("/index.html").forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();

        try {
            // 1. 校验是否为文件上传请求
            if (!ServletFileUpload.isMultipartContent(req)) {
                out.write(buildErrorHtml("请通过文件上传表单提交!"));
                return;
            }

            // 2. 初始化文件上传组件
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setFileSizeMax(5 * 1024 * 1024); // 限制文件大小 5MB(与 server.js 一致)

            // 3. 解析上传文件
            List<FileItem> items = upload.parseRequest(req);
            FileItem fileItem = null;
            for (FileItem item : items) {
                if (!item.isFormField() && "mdFile".equals(item.getFieldName())) { // 对应前端表单的 name="mdFile"
                    fileItem = item;
                    break;
                }
            }

            // 4. 校验文件是否存在
            if (fileItem == null) {
                out.write(buildErrorHtml("请选择要上传的文件!"));
                return;
            }

            // 5. 校验文件类型(仅支持 .md 和 .txt,与 server.js 一致)
            String fileName = fileItem.getName();
            String fileExt = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
            if (!".md".equals(fileExt) && !".txt".equals(fileExt)) {
                out.write(buildErrorHtml("仅支持上传 .md 或 .txt 格式的文件!"));
                return;
            }

            // 6. 读取文件内容
            String fileContent = new String(fileItem.getInputStream().readAllBytes(), "UTF-8");
            String renderContent = fileContent;

            // 7. 若为 TXT 文件,执行转换(参考 server.js 的 txt 处理逻辑)
            if (".txt".equals(fileExt)) {
                CsvToMermaidUtil.Result convertResult = CsvToMermaidUtil.validateAndConvert(fileName, fileContent);
                if (!convertResult.isValid()) {
                    out.write(buildErrorHtml(convertResult.getContent()));
                    return;
                }
                renderContent = convertResult.getContent();
            }

            // 8. Markdown 渲染为 HTML(含 Mermaid 代码块转换)
            String html = HTML_RENDERER.render(MARKDOWN_PARSER.parse(renderContent));

            // 9. 拼接最终 HTML(注入渲染结果到页面,加载 Mermaid.js)
            String finalHtml = buildFinalHtml(html);
            out.write(finalHtml);

        } catch (Exception e) {
            e.printStackTrace();
            out.write(buildErrorHtml("文件处理出错,请稍后重试!"));
        } finally {
            out.close();
        }
    }

    /**
     * 构建错误页面(参考 server.js 的错误响应)
     */
    private String buildErrorHtml(String message) {
        return "<!DOCTYPE html>" +
                "<html lang='zh-CN'>" +
                "<head><meta charset='UTF-8'><title>处理失败</title></head>" +
                "<body style='text-align:center;margin-top:50px;'>" +
                "<h1 style='color:red;'>处理失败</h1>" +
                "<p style='font-size:18px;'>" + message + "</p>" +
                "<a href='/' style='font-size:16px;'>返回上传页面</a>" +
                "</body></html>";
    }

    /**
     * 构建最终渲染页面(注入 Mermaid.js,确保图表能在前端渲染)
     */
    private String buildFinalHtml(String renderedMdHtml) {
        return "<!DOCTYPE html>" +
                "<html lang='zh-CN'>" +
                "<head>" +
                "<meta charset='UTF-8'>" +
                "<title>Markdown + Mermaid 渲染结果</title>" +
                "<style>" +
                "  body { max-width: 1200px; margin: 0 auto; padding: 20px; font-family: sans-serif; }" +
                "  .mermaid { margin: 20px 0; }" +
                "  h1 { color: #2c3e50; }" +
                "  blockquote { color: #7f8c8d; border-left: 4px solid #bdc3c7; padding-left: 10px; }" +
                "</style>" +
                // 引入 Mermaid 10.9 前端库(本地或CDN均可)
                "<script src='./static/js/mermaid.min.js'></script>" +
                "<script>" +
                "  // 初始化 Mermaid(确保图表渲染)" +
                "  mermaid.initialize({ startOnLoad: true, theme: 'default' });" +
                "</script>" +
                "</head>" +
                "<body>" +
                renderedMdHtml + // 注入 Flexmark 渲染后的 HTML(含 <div class="mermaid">)
                "</body></html>";
    }
}
5. 前端上传页面(index.html)

放在 webapp/ 目录下,提供文件选择和上传功能(对应 server.js 的 public/index.html)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Markdown + Mermaid 上传渲染工具</title>
    <style>
        body { max-width: 800px; margin: 0 auto; padding: 50px 20px; text-align: center; }
        .upload-container { border: 2px dashed #3498db; padding: 40px; border-radius: 8px; margin-top: 30px; }
        input[type="file"] { margin: 20px 0; padding: 10px; font-size: 16px; }
        button { background: #3498db; color: white; border: none; padding: 12px 30px; font-size: 18px; border-radius: 4px; cursor: pointer; }
        button:hover { background: #2980b9; }
        .tip { color: #7f8c8d; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>Markdown + Mermaid 上传渲染工具</h1>
    <div class="upload-container">
        <form action="/upload-md" method="post" enctype="multipart/form-data">
            <input type="file" name="mdFile" accept=".md,.txt" required>
            <br>
            <button type="submit">上传并渲染</button>
        </form>
        <p class="tip">支持格式:.md(直接渲染)、.txt(需符合 source,target,weight 格式,自动转换为Mermaid关系图)</p>
        <p class="tip">文件大小限制:5MB</p>
    </div>
</body>
</html>
6. Mermaid 前端库(mermaid.min.js)
  1. 下载 Mermaid 10.9.5 版本
    curl -O https://cdn.jsdelivr.net/npm/mermaid@10.9.5/dist/mermaid.min.js
  2. 放在 webapp/static/js/ 目录下(与 index.html 中引入路径一致)
四、部署与运行步骤
  1. 打包项目 :使用 Maven 执行 mvn clean package,生成 MermaidMarkdownWeb.war 文件
  2. 部署到 Tomcat
    • 将 war 文件复制到 Tomcat 的 webapps/ 目录下
    • 启动 Tomcat(执行 bin/startup.batbin/startup.sh
  3. 访问应用
    • 浏览器输入 http://localhost:8080/MermaidMarkdownWeb(默认 Tomcat 端口 8080)
    • 若修改 Tomcat 端口,需对应调整访问地址
五、功能验证(与 server.js 逻辑一致)
  1. 上传 .md 文件(含 Mermaid 语法):

    • 示例 test1.md(与文档3一致):

      markdown 复制代码
      ### 转账关系图
      ```mermaid
      graph LR
          A[张三] -->|10000| B[李四]
          B[李四] -->|9000| C[王五]
          C[王五] -->|8000| D[刘哥]
          D[刘哥] -->|5000| A[张三]
          D[刘哥] -->|3000| B[李四]
          D[刘哥] -->|2000| C[王五]
      复制代码
    • 上传后会直接渲染 Markdown 内容和 Mermaid 流程图

  2. 上传 .txt 文件(符合 CSV 格式):

    • 示例 test1.txt(与文档2一致):

      复制代码
      source,target,weight
      张三,李四,10000
      李四,王五,9000
      王五,刘哥,8000
      刘哥,张三,5000
      刘哥,李四,3000
      刘哥,王五,2000
    • 上传后会自动转换为 Mermaid 代码块,再渲染为流程图

  3. 错误场景验证

    • 上传非 .md/.txt 文件:提示"仅支持上传 .md 或 .txt 格式的文件!"
    • 上传格式错误的 .txt 文件(首行不是 source,target,weight):提示".txt 文本文件格式错"
    • 上传空文件:提示".txt 文本文件格式错"
六、关键逻辑对齐说明(与 server.js 对比)
功能点 server.js(Node.js)实现 Java(Tomcat+Flexmark)实现
文件类型校验 multer 的 fileFilter 过滤 .md/.txt Servlet 中判断文件后缀
文件大小限制 multer 配置 limits: 5MB ServletFileUpload 设置 setFileSizeMax(5MB)
TXT→Mermaid 转换 解析 CSV 格式,生成 graph LR 语法 完全相同的解析逻辑和 Mermaid 语法生成
Mermaid 渲染支持 markdown-it 插件生成 Flexmark-mermaid 扩展生成相同格式标签
前端渲染 页面引入 mermaid.js 相同方式引入 mermaid 10.9 版本
错误处理 返回 HTML 错误页面+返回按钮 相同的错误页面样式和跳转逻辑
七、注意事项
  1. Tomcat 端口冲突 :若 8080 端口被占用,修改 conf/server.xml 中的 Connector port="8080" 为其他端口(如 8000)
  2. 文件编码:所有文件(.md/.txt)需使用 UTF-8 编码,避免中文乱码
  3. Mermaid 语法支持:支持流程图(graph LR/TD)、时序图、类图等 Mermaid 10.9 支持的所有语法
  4. 静态资源访问 :确保 mermaid.min.js 路径正确,若部署时修改了项目上下文路径(如 /mermaid),需同步调整 index.html 中 <script src> 路径

该方案完全基于 Java 技术栈实现,与原 Node.js 方案逻辑一致,可直接部署运行,支持 Markdown 原生 Mermaid 渲染和 TXT 格式转换功能。

相关推荐
Elias不吃糖2 小时前
Day1 项目启动记录(KnowledgeDock)
java·springboot·登陆·项目启动
安全检测中2 小时前
序列化与反序列化学习
java·开发语言
我是咸鱼不闲呀2 小时前
力扣Hot100系列18(Java)——[技巧]总结 (只出现一次的数字,多数元素,颜色分类,下一个排列,寻找重复数)
java·算法·leetcode
_周游2 小时前
Java8 API文档搜索引擎_优化构建索引速度
java·服务器·搜索引擎·intellij-idea
北凉军2 小时前
IDEA中热部署插件JRebel激活失败404
java·ide·intellij-idea
乐观甜甜圈2 小时前
在Windows系统上hprof文件是否可以删除
java
野犬寒鸦2 小时前
从零起步学习并发编程 || 第二章:多线程与死锁在项目中的应用示例
java·开发语言·数据库·后端·学习
张np2 小时前
java进阶-Zookeeper
java·zookeeper·java-zookeeper
long3162 小时前
合并排序 merge sort
java·数据结构·spring boot·算法·排序算法