内网 MySQL 表结构一键导出 Word 文档:直连 + Agent 双模式实战

一、功能简述

在线工具站(onltool.site)有一个"MySQL 表结构文档"小工具:输入数据库连接信息,点击导出,下载一个排版整齐的 Word 设计文档。

体验:

复制代码
输入 IP / 端口 / 用户名 / 密码 / 数据库名 → 点导出 → 浏览器下载 doc 文件

文档包含每张表的表名、注释、字段序号、字段名、类型、是否必填、备注,用表格清晰展示。


二、技术选型:为什么用 FreeMarker 而不是 POI?

方案 复杂度 排版控制 文件体积
Apache POI 编程式生成 精确
FreeMarker 模板填充 灵活
HTML 另存为 .doc

选择 FreeMarker:用 Word XML 模板(.ftl),表结构数据填充进去,生成的 .doc 文件排版精确、体积小、兼容 WPS 和 Office。


三、直连模式的实现

3.1 后端采集数据

java 复制代码
// 1. 查 information_schema 获取所有表
SELECT TABLE_NAME, TABLE_COMMENT 
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = ? 
ORDER BY TABLE_NAME

// 2. 每张表查字段结构
SHOW FULL COLUMNS FROM `table_name`

3.2 构建数据模型

java 复制代码
List<Map<String, Object>> tableList = new ArrayList<>();
for (表) {
    Map<String, Object> tableMap = new HashMap<>();
    tableMap.put("name", "users(用户表)");  // 表名+注释
    
    List<Map<String, String>> structures = new ArrayList<>();
    for (字段) {
        Map<String, String> col = new LinkedHashMap<>();
        col.put("code", "1");          // 序号
        col.put("field", "id");        // 字段名
        col.put("type", "int(11)");    // 类型
        col.put("isNull", "是");        // 是否必填
        col.put("comment", "主键ID");   // 备注
        structures.add(col);
    }
    tableMap.put("structure", structures);
    tableList.add(tableMap);
}

3.3 FreeMarker 模板渲染

使用 6000 行的 Word XML 模板(tables.ftl),包含字体、页边距、标题样式、表格边框等全部排版信息。数据填充只占模板的极小部分:

xml 复制代码
<#list tables as table>
  <w:p><w:r><w:t>${table.name}</w:t></w:r></w:p>
  <#list table.structure as col>
    <w:tr>
      <w:tc><w:p><w:r><w:t>${col.code}</w:t></w:r></w:p></w:tc>
      <w:tc><w:p><w:r><w:t>${col.field}</w:t></w:r></w:p></w:tc>
      ...
    </w:tr>
  </#list>
</#list>

3.4 返回文件

java 复制代码
ByteArrayOutputStream baos = new ByteArrayOutputStream();
template.process(dataMap, new BufferedWriter(new OutputStreamWriter(baos)));

return ResponseEntity.ok()
    .header("Content-Disposition", "attachment; filename*=UTF-8''数据库_表结构.doc")
    .contentType(MediaType.APPLICATION_OCTET_STREAM)
    .body(baos.toByteArray());

四、Agent 模式:当数据库在内网

云端服务器无法直接连接用户内网的 MySQL。与 SQL 查询工具一样,这里也用 WebSocket + 本地 Agent 隧道。

4.1 第一版:Agent 自己生成 Word(踩坑)

最初让 Agent 连 MySQL、查表结构、生成 HTML 转 Word、base64 回传。问题:

问题 1:样式不一致。 Agent 用的简易 HTML 样式,后端直连用的是 6000 行 XML 模板,两个 Word 文档排版完全不同。

问题 2:消息体过大。 8 张表的 Word 文档 base64 后超过 Spring Boot 默认的 64KB WebSocket 缓冲区上限,服务器直接关闭连接:

复制代码
The decoded text message was too big for the output buffer
and the endpoint does not support partial messages

4.2 第二版:Agent 只采集数据,后端统一渲染

推翻重来,改为:

复制代码
Agent 连 MySQL → 查表结构 → 返回 JSON 数据 → 前端 POST 给后端 → 后端 FreeMarker 渲染 → 下载

Agent 只做数据采集:

python 复制代码
def handle_table_structure(ws, msg):
    conn = pymysql.connect(host=host, port=port, user=user, ...)
    
    # 查所有表
    cursor.execute("SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema...")
    tables = cursor.fetchall()
    
    table_data = []
    for table_name, comment in tables:
        cursor.execute(f"SHOW FULL COLUMNS FROM `{table_name}`")
        columns = cursor.fetchall()
        cols = [{"field": c[0], "type": c[1], "required": ..., "comment": c[8]} for c in columns]
        table_data.append({"name": table_name, "comment": comment, "columns": cols})
    
    send(ws, {"type": "tableStructureResult", "database": database, "tables": table_data})

后端新增 Agent 模式入口:

java 复制代码
@PostMapping("/export")
public ResponseEntity<byte[]> export(@RequestBody Map<String, Object> body) {
    List<Map<String, Object>> tableList = new ArrayList<>();

    // Agent 模式:数据已由 Agent 采集好
    if (body.containsKey("tables")) {
        for (Map<String, Object> t : (List) body.get("tables")) {
            // 构建与直连模式完全相同的数据结构
            tableMap.put("name", displayName);
            tableMap.put("structure", structures);
            tableList.add(tableMap);
        }
        return renderWord(database, tableList);  // 共用 FreeMarker 渲染
    }

    // 直连模式:后端自己连 MySQL
    ...
    return renderWord(database, tableList);
}

前端中继:

javascript 复制代码
case 'tableStructureResult':
  // 收到 Agent 的 JSON 数据 → 发给后端生成 Word
  request({
    url: '/api/pub/table-structure/export',
    method: 'post',
    data: { database: msg.database, tables: msg.tables },
    responseType: 'blob'
  }).then(res => {
    // 触发浏览器下载
    const url = window.URL.createObjectURL(new Blob([res]))
    const a = document.createElement('a')
    a.href = url; a.download = msg.database + '_table_structure.doc'
    a.click()
  })
  break

五、关键坑点

5.1 WebSocket 消息体超限

Spring Boot + Tomcat 默认 WebSocket 文本消息缓冲区 64KB 。数据库表多时,JSON 数据体轻松超标。配置 ServletServerContainerFactoryBean 扩到 50MB:

java 复制代码
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
    ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
    container.setMaxTextMessageBufferSize(50 * 1024 * 1024);  // 50 MB
    container.setMaxSessionIdleTimeout(0L);
    return container;
}

5.2 竞态条件导致结果丢失

Agent 返回 JSON 时,如果浏览器 WebSocket 恰好断开重连,后端 removeBrowser 可能误清掉新连接的 session:

java 复制代码
// 修复:只清理自己的 session
if (pair.browserSession == browserSession) {
    pair.browserSession = null;
}

5.3 样式一致性

Agent 不生成 Word,只回传结构化数据。所有 Word 文档统一走后端 FreeMarker 模板渲染,保证直连和 Agent 两种模式输出的文档排版完全一致。


六、结果展示

小工具页面

导出设计文档截图


七、架构总结

复制代码
直连模式:前端 → 后端(连MySQL + FreeMarker) → Word blob → 下载
Agent模式:前端 → WebSocket → Agent(连MySQL, 回传JSON) → 前端 → 后端(FreeMarker) → Word blob → 下载
                                                              ↑
                                                    两种模式在此汇合

核心设计原则:Agent 只做它擅长的事(穿透内网),Word 生成永远交给后端的同一套 FreeMarker 模板

项目在线地址:https://onltool.site

相关推荐
向日的葵0062 小时前
Redis会话机制vsJWT机制深度解析
数据库·redis·python·缓存·系统架构·jwt
bosins2 小时前
如何配置SQL Server数据库定时自动备份
数据库
烟雨归来2 小时前
RAC数据库OS进程消失,GV$SESSION长期残留KILLED僵尸会话处理
数据库·oracle
小龙报2 小时前
【优选算法】1. 水果成蓝 2.找到字符串中所有字母的异位词
java·c语言·数据结构·数据库·c++·redis·算法
宇宙第一小趴菜3 小时前
六、Oracle 目录结构
数据库·oracle
IvorySQL3 小时前
PG 日报|修复高 IO 并发场景,解决预读机制耗尽本地缓冲区缺陷
数据库·人工智能·sql·postgresql·区块链
IT小盘3 小时前
08-FastAPI加MySQL实现AI对话记录持久化
android·mysql·fastapi
TiDB 社区干货传送门3 小时前
TiDB 8.5.7 发版|六项实用功能上线
数据库·mysql·tidb
在繁华处3 小时前
MySQL 学习笔记 01:从概念到约束,搭好库表骨架
数据库·oracle