Java:Tomcat 9, flexmark 0.64 和 mermaid.min.js 10.9 实现 Markdown 中 Mermaid 图表的渲染
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)
- 下载 Mermaid 10.9.5 版本
curl -O https://cdn.jsdelivr.net/npm/mermaid@10.9/dist/mermaid.min.js - 放在
webapp/static/js/目录下(与 index.html 中引入路径一致)
四、部署与运行步骤
- 打包项目:使用 Maven 执行
mvn clean package,生成MarkdownWeb.war文件 - 部署到 Tomcat:
- 将 war 文件复制到 Tomcat 的
webapps/目录下 - 启动 Tomcat(执行
bin/startup.bat或bin/startup.sh)
- 将 war 文件复制到 Tomcat 的
- 访问应用:
- 浏览器输入
http://localhost:8080/MarkdownWeb(默认 Tomcat 端口 8080) - 若修改 Tomcat 端口,需对应调整访问地址
- 浏览器输入
五、功能验证(与 server.js 逻辑一致)
-
上传 .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 流程图
- 示例 test1.md(与文档3一致):
-
上传 .txt 文件(符合 CSV 格式):
- 示例 test1.txt(与文档2一致):
source,target,weight 张三,李四,10000 李四,王五,9000 王五,刘哥,8000 刘哥,张三,5000 刘哥,李四,3000 刘哥,王五,2000 - 上传后会自动转换为 Mermaid 代码块,再渲染为流程图
- 示例 test1.txt(与文档2一致):
-
错误场景验证:
- 上传非 .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 错误页面+返回按钮 | 相同的错误页面样式和跳转逻辑 |
七、注意事项
- Tomcat 端口冲突:若 8080 端口被占用,修改
conf/server.xml中的Connector port="8080"为其他端口(如 8000) - 文件编码:所有文件(.md/.txt)需使用 UTF-8 编码,避免中文乱码
- Mermaid 语法支持:支持流程图(graph LR/TD)、时序图、类图等 Mermaid 10.9 支持的所有语法
- 静态资源访问:确保 mermaid.min.js 路径正确,若部署时修改了项目上下文路径(如
/mermaid),需同步调整 index.html 中<script src>路径
该方案完全基于 Java 技术栈实现,与原 Node.js 方案逻辑一致,可直接部署运行,支持 Markdown 原生 Mermaid 渲染和 TXT 格式转换功能。
webapp\WEB-IF\web.xml 如何配置?
在 Eclipse 项目 MarkdownWeb 中 src\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.html、500.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 中配置注意事项
- 文件位置校验:确保
web.xml放在src/main/webapp/WEB-INF/下,Eclipse 中会自动识别为 Web 应用配置文件; - Tomcat 版本匹配:
web-app的version="4.0"对应 Tomcat 9,若误用 Tomcat 8 需改为3.1,Tomcat 10 需改为5.0(但 Tomcat 10 不兼容javax.servlet,建议保持 Tomcat 9); - 编码问题:保存
web.xml时选择 UTF-8 编码(Eclipse 中右键文件 → Properties → Text file encoding → UTF-8); - 部署验证:配置完成后,在 Eclipse 中右键项目 → Run As → Run on Server,选择 Tomcat 9,访问
http://localhost:8080/MarkdownWeb验证是否加载 index.html。
总结
web.xml核心作用是配置 Servlet 映射、字符编码过滤器、欢迎页,解决中文乱码和请求路由问题;- 注解和 xml 配置二选一,新手推荐用 xml 配置(更易排查问题);
- 必须配置
CharacterEncodingFilter确保中文上传/渲染无乱码; - 配置完成后,重启 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 实体,如 < → <)
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("<", "<")
.replace(">", ">")
.replace("&", "&")
.replace(""", "\"")
.replace("'", "'")
.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/目录下。
二、核心逻辑说明
- 为什么移除 flexmark-ext-mermaid 后需要手动替换?
Flexmark 原生渲染```mermaid代码块时,会生成<pre><code class="language-mermaid">代码</code></pre>,而前端 Mermaid.js 只识别<div class="mermaid">代码</div>标签,因此需要手动替换标签格式。 - HTML 实体解码:Flexmark 会将代码中的
< > &等符号转义为 HTML 实体(如<→<),必须解码后 Mermaid.js 才能正确识别语法。 - 兼容性:该方案不依赖任何第三方 Mermaid 扩展,仅通过“基础 Markdown 渲染 + 正则替换标签 + 前端渲染”实现,兼容性更强。
三、验证步骤
- 重新打包项目:
mvn clean package; - 部署到 Tomcat 9 并启动;
- 上传包含 Mermaid 代码块的
.md文件或符合格式的.txt文件; - 页面会正确渲染 Markdown 内容,并通过前端 Mermaid.js 渲染出流程图。
总结
- 移除了不存在的
flexmark-ext-mermaid依赖,仅保留 Flexmark 核心包,避免依赖缺失问题; - 新增
MermaidBlockParser工具类,通过正则手动替换 Mermaid 代码块标签,适配前端渲染; - 核心逻辑不变:TXT 文件转换为 Mermaid Markdown 代码块 → 基础 Markdown 渲染 → 替换标签 → 前端 Mermaid.js 渲染;
- 所有配置和前端页面保持兼容,无需额外调整即可运行。
更多推荐



所有评论(0)