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

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

一、技术栈说明
组件 版本要求 核心作用
Tomcat 9.0.x Java Web 服务器,部署 Web 应用
Flexmark 0.64.8(兼容0.64) Markdown 解析渲染核心库
Flexmark-ext-mermaid 0.64.8 Mermaid 语法扩展插件
Mermaid.js 10.9.0 前端渲染 Mermaid 图表(流程图/关系图等)
JDK 8+ 运行 Java 应用
二、项目结构(Maven Web 工程)
MarkdownWeb/
├── 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)
<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[[[[[MarkdownWeb</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>MarkdownWeb</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 插件逻辑)

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 代码块的转换

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 接口

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)

<!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>
            <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/dist/mermaid.min.js
  2. 放在 webapp/static/js/ 目录下(与 index.html 中引入路径一致)
四、部署与运行步骤
  1. 打包项目:使用 Maven 执行 mvn clean package,生成 MarkdownWeb.war 文件
  2. 部署到 Tomcat
    • 将 war 文件复制到 Tomcat 的 webapps/ 目录下
    • 启动 Tomcat(执行 bin/startup.batbin/startup.sh
  3. 访问应用
    • 浏览器输入 http://localhost:8080/MarkdownWeb(默认 Tomcat 端口 8080)
    • 若修改 Tomcat 端口,需对应调整访问地址
五、功能验证(与 server.js 逻辑一致)
  1. 上传 .md 文件(含 Mermaid 语法):

    • 示例 test1.md(与文档3一致):
      ### 转账关系图
      ```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 格式转换功能。


webapp\WEB-IF\web.xml 如何配置?

在 Eclipse 项目 MarkdownWebsrc\main\webapp\WEB-INF\web.xml 文件的具体配置方式,这个文件是 Tomcat 识别 Web 应用的核心配置文件,结合之前实现的 Tomcat 9 + Flexmark + Mermaid 功能,请提供完整、可直接复制的 web.xml 配置,并解释关键配置项的作用。

一、完整的 web.xml 配置内容

将以下代码复制到 \eclipse-workspace\MarkdownWeb\src\main\webapp\WEB-INF\web.xml 文件中(如果文件不存在则新建):

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="false">

    <!-- 1. Web 应用基本信息 -->
    <display-name>MarkdownWeb</display-name>
    <description>Markdown + Mermaid 渲染 Web 应用(Tomcat 9)</description>
    
    <!-- 2. 欢迎页配置(访问根路径时默认加载 index.html) -->
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- 3. Servlet 映射(替代注解 @WebServlet,二选一即可) -->
    <!-- 如果你保留了 Controller 中的 @WebServlet 注解,此部分可省略 -->
    <servlet>
        <servlet-name>MarkdownController</servlet-name>
        <servlet-class>com.mermaid.controller.MarkdownController</servlet-class>
        <!-- 可选:设置文件上传的临时目录(解决大文件上传临时文件存放问题) -->
        <init-param>
            <param-name>javax.servlet.context.tempdir</param-name>
            <param-value>${catalina.base}/temp</param-value>
        </init-param>
        <!-- 启动优先级:数字越小,Tomcat 启动时越先加载 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>MarkdownController</servlet-name>
        <!-- 映射根路径和上传接口,与 @WebServlet(urlPatterns = {"/", "/upload-md"}) 一致 -->
        <url-pattern>/</url-pattern>
        <url-pattern>/upload-md</url-pattern>
    </servlet-mapping>

    <!-- 4. 文件上传配置(解决 Commons FileUpload 解析表单的问题) -->
    <mime-mapping>
        <extension>md</extension>
        <mime-type>text/markdown</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>txt</extension>
        <mime-type>text/plain</mime-type>
    </mime-mapping>

    <!-- 5. 字符编码过滤器(关键:解决中文乱码问题) -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>ignore</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <!-- 对所有请求生效 -->
        <url-pattern>/*</url-pattern>
        <!-- 覆盖 GET/POST 等请求方法 -->
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- 6. 错误页面配置(可选:统一处理异常) -->
    <error-page>
        <error-code>404</error-code>
        <location>/error/404.html</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/error/500.html</location>
    </error-page>
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/error/error.html</location>
    </error-page>

</web-app>

二、关键配置项解释

1. 核心配置说明
配置项 作用
web-app version="4.0" 对应 Tomcat 9 支持的 Servlet 4.0 规范(Tomcat 9 不支持更高版本)
metadata-complete="false" 允许使用注解(如 @WebServlet),若设为 true 则仅识别 web.xml 配置
welcome-file-list 访问 http://localhost:8080/MarkdownWeb 时,默认加载 index.html
CharacterEncodingFilter 强制所有请求/响应使用 UTF-8 编码,彻底解决中文乱码(上传/渲染关键)
Servlet 映射 MarkdownController 绑定到 //upload-md 路径,替代注解
2. 与注解的兼容说明
  • 如果你在 MarkdownController 中保留了 @WebServlet(urlPatterns = {"/", "/upload-md"}) 注解,可以删除 web.xml 中的 <servlet><servlet-mapping> 部分(注解和 xml 配置二选一即可);
  • 建议新手使用 web.xml 配置(更直观,便于调试),注解方式更简洁但配置分散。
3. 可选的错误页面配置

如果需要自定义错误页面,需在 webapp/ 目录下新建 error 文件夹,创建 404.html500.html 等文件,示例(404.html):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>页面不存在</title>
</head>
<body style="text-align:center;margin-top:50px;">
    <h1>404 - 页面不存在</h1>
    <p>你访问的路径不存在,请返回 <a href="/MarkdownWeb">首页</a></p>
</body>
</html>

三、Eclipse 中配置注意事项

  1. 文件位置校验:确保 web.xml 放在 src/main/webapp/WEB-INF/ 下,Eclipse 中会自动识别为 Web 应用配置文件;
  2. Tomcat 版本匹配web-appversion="4.0" 对应 Tomcat 9,若误用 Tomcat 8 需改为 3.1,Tomcat 10 需改为 5.0(但 Tomcat 10 不兼容 javax.servlet,建议保持 Tomcat 9);
  3. 编码问题:保存 web.xml 时选择 UTF-8 编码(Eclipse 中右键文件 → Properties → Text file encoding → UTF-8);
  4. 部署验证:配置完成后,在 Eclipse 中右键项目 → Run As → Run on Server,选择 Tomcat 9,访问 http://localhost:8080/MarkdownWeb 验证是否加载 index.html。

总结

  1. web.xml 核心作用是配置 Servlet 映射、字符编码过滤器、欢迎页,解决中文乱码和请求路由问题;
  2. 注解和 xml 配置二选一,新手推荐用 xml 配置(更易排查问题);
  3. 必须配置 CharacterEncodingFilter 确保中文上传/渲染无乱码;
  4. 配置完成后,重启 Tomcat 即可生效,无需额外修改代码。

我发现 flexmark-ext-mermaid 这个包不存在,请移除该依赖并重新实现 Markdown 中 Mermaid 图表的渲染方案。核心思路是:不依赖第三方 Mermaid 扩展,而是在 Flexmark 解析 Markdown 后,手动识别 ```mermaid 代码块并替换为 Mermaid 前端渲染所需的 <div class="mermaid"> 标签,最终通过前端 Mermaid.js 完成渲染。

一、调整后的完整实现方案(移除 flexmark-ext-mermaid)

1. 首先修改 Maven 依赖(pom.xml)

移除 flexmark-ext-mermaid 依赖,仅保留 Flexmark 核心包:

<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>MarkdownWeb</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>Markdown + Mermaid 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>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>MarkdownWeb</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)

移除 Mermaid 扩展配置,仅保留基础 Markdown 解析能力:

package com.mermaid.config;

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

public class FlexmarkConfig {
    // 初始化 Markdown 解析器(仅保留基础功能)
    public static Parser getParser() {
        MutableDataSet options = new MutableDataSet();
        // 启用 HTML 块/行内 HTML、自动链接(基础功能)
        options.set(Parser.ENABLE_HTML_BLOCKS, true);
        options.set(Parser.ENABLE_HTML_INLINE, true);
        options.set(Parser.LINKIFY, true);
        return Parser.builder(options).build();
    }

    // 初始化 HTML 渲染器(无 Mermaid 扩展)
    public static HtmlRenderer getHtmlRenderer() {
        MutableDataSet options = new MutableDataSet();
        return HtmlRenderer.builder(options).build();
    }
}
3. 新增 Mermaid 代码块替换工具类(MermaidBlockParser.java)

核心逻辑:手动识别 Markdown 渲染后的 <pre><code class="language-mermaid"> 标签,替换为 <div class="mermaid">(前端 Mermaid.js 识别的标签):

package com.mermaid.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 手动解析 Markdown 中的 Mermaid 代码块,替换为前端可渲染的标签
 */
public class MermaidBlockParser {
    // 匹配 Flexmark 渲染后的 mermaid 代码块正则
    private static final Pattern MERMAID_CODE_PATTERN = Pattern.compile(
        "<pre><code class=\"language-mermaid\">([\\s\\S]*?)</code></pre>",
        Pattern.CASE_INSENSITIVE
    );

    /**
     * 替换 HTML 中的 Mermaid 代码块为 <div class="mermaid">
     * @param html Flexmark 渲染后的原始 HTML
     * @return 替换后的 HTML(支持前端 Mermaid.js 渲染)
     */
    public static String replaceMermaidBlocks(String html) {
        if (html == null || html.isEmpty()) {
            return html;
        }

        Matcher matcher = MERMAID_CODE_PATTERN.matcher(html);
        StringBuilder result = new StringBuilder();
        int lastEnd = 0;

        while (matcher.find()) {
            // 追加匹配前的内容
            result.append(html.substring(lastEnd, matcher.start()));
            // 获取 Mermaid 代码内容(解码 HTML 实体,如 &lt; → <)
            String mermaidCode = decodeHtmlEntities(matcher.group(1));
            // 替换为 Mermaid 前端渲染标签
            result.append("<div class=\"mermaid\">").append(mermaidCode).append("</div>");
            lastEnd = matcher.end();
        }
        // 追加剩余内容
        result.append(html.substring(lastEnd));
        return result.toString();
    }

    /**
     * 解码 HTML 实体(解决 Flexmark 转义的 < > 等符号)
     */
    private static String decodeHtmlEntities(String str) {
        return str.replace("&lt;", "<")
                  .replace("&gt;", ">")
                  .replace("&amp;", "&")
                  .replace("&quot;", "\"")
                  .replace("&#39;", "'")
                  .replace("<br>", "\n"); // 替换换行符
    }
}
4. 修改核心控制器(MarkdownController.java)

在 Markdown 渲染后,调用工具类替换 Mermaid 代码块:

package com.mermaid.controller;

import com.mermaid.config.FlexmarkConfig;
import com.mermaid.util.CsvToMermaidUtil;
import com.mermaid.util.MermaidBlockParser;
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 {
        // 跳转前端上传页面
        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 限制

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

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

            // 5. 校验文件类型(仅支持 .md 和 .txt)
            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 文件,转换为 Mermaid Markdown 代码块
            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
            String rawHtml = HTML_RENDERER.render(MARKDOWN_PARSER.parse(renderContent));
            // 9. 关键:手动替换 Mermaid 代码块为前端可渲染标签
            String finalHtml = MermaidBlockParser.replaceMermaidBlocks(rawHtml);

            // 10. 拼接最终页面(注入 Mermaid.js)
            out.write(buildFinalHtml(finalHtml));

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

    /**
     * 构建错误页面
     */
    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 标签)" +
                "  mermaid.initialize({ startOnLoad: true, theme: 'default' });" +
                "</script>" +
                "</head>" +
                "<body>" +
                renderedMdHtml +
                "</body></html>";
    }
}
5. 其他文件保持不变
  • CsvToMermaidUtil.java(TXT 转 Mermaid 代码块逻辑)、index.html(前端上传页面)、web.xml(Web 配置)无需修改;
  • 确保 mermaid.min.js 10.9 放在 webapp/static/js/ 目录下。

二、核心逻辑说明

  1. 为什么移除 flexmark-ext-mermaid 后需要手动替换?
    Flexmark 原生渲染 ```mermaid 代码块时,会生成 <pre><code class="language-mermaid">代码</code></pre>,而前端 Mermaid.js 只识别 <div class="mermaid">代码</div> 标签,因此需要手动替换标签格式。
  2. HTML 实体解码:Flexmark 会将代码中的 < > & 等符号转义为 HTML 实体(如 <&lt;),必须解码后 Mermaid.js 才能正确识别语法。
  3. 兼容性:该方案不依赖任何第三方 Mermaid 扩展,仅通过“基础 Markdown 渲染 + 正则替换标签 + 前端渲染”实现,兼容性更强。

三、验证步骤

  1. 重新打包项目:mvn clean package
  2. 部署到 Tomcat 9 并启动;
  3. 上传包含 Mermaid 代码块的 .md 文件或符合格式的 .txt 文件;
  4. 页面会正确渲染 Markdown 内容,并通过前端 Mermaid.js 渲染出流程图。

总结

  1. 移除了不存在的 flexmark-ext-mermaid 依赖,仅保留 Flexmark 核心包,避免依赖缺失问题;
  2. 新增 MermaidBlockParser 工具类,通过正则手动替换 Mermaid 代码块标签,适配前端渲染;
  3. 核心逻辑不变:TXT 文件转换为 Mermaid Markdown 代码块 → 基础 Markdown 渲染 → 替换标签 → 前端 Mermaid.js 渲染;
  4. 所有配置和前端页面保持兼容,无需额外调整即可运行。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐