TinyMCE导入pdf识别书签和目录结构

北京XX央企CMS系统新闻模块升级项目实施记录
(基于信创多环境的Vue2+TinyMCE+SpringBoot集成方案)

一、项目背景与需求分析

1. 现状与痛点
  • 系统环境:央企内部CMS系统,前端采用Vue2-cli,编辑器为TinyMCE 5.x,后端为SpringBoot 2.7.x,数据库需兼容达梦DM8人大金仓V8/V9
  • 信创要求:需支持Windows/macOS(开发测试)统信UOS、中标麒麟、银河麒麟(龙芯/飞腾架构)生产环境,硬件覆盖龙芯3A5000/3C5000
  • 核心需求
    • 实现Word文档一键导入,自动解析文本、图片、表格、标题样式(如字体、颜色、段落间距)。
    • 图片自动上传至集团私有MinIO存储(兼容信创环境),生成HTTPS访问链接。
    • 提供7×24小时技术支持,保障金融级系统稳定性(SLA≥99.99%)。

二、技术选型与产品评估

1. 前端编辑器方案对比
方案优势劣势适配性评估
TinyMCE官方插件原生支持Vue2集成,通过paste事件拦截实现Word内容过滤;提供基础样式保留能力需二次开发处理复杂样式(如多级列表、嵌套表格);图片上传需对接后端API核心方案,社区支持稳定
Mammoth.js+自定义解析专注Word结构解析,能保留标题、列表等语义化标签图片处理需额外开发;对表格样式支持较弱需结合TinyMCE使用,复杂度高
商业编辑器(如EpicEditor)功能完整,支持Word一键导入+云存储;提供信创认证版本授权费用高(约25万元/年);需替换现有编辑器,迁移成本大暂不考虑
开源插件(如WordPaster)功能完整,支持Word,excel,ppt,pdf一键导入+一键粘贴;提供信创认证版本完全开放产品源代码;不需要替换现有编辑器,整合简单,迁移成本最小意向方案

结论:选择TinyMCE官方插件+自定义样式映射逻辑,通过拦截粘贴事件触发Word解析流程。

2. 后端服务选型
  • 文件处理:使用Apache POI解析.docx文件,提取文本、图片、元数据;针对龙芯架构优化内存占用(限制单个文件处理大小≤50MB)。
  • 图片存储:对接集团MinIO对象存储(部署于银河麒麟服务器),配置S3兼容接口,生成带时效性的预签名URL。
  • 数据库适配
    • 达梦DM8:修改JPA的@Table注解,指定表空间策略(@Table(tablespace = "XX_CMS_TS"))。
    • 人大金仓V9:使用jdbc:kingbase://host:port/dbname连接串,配置连接池参数(maxActive=50, maxWait=60000)。

三、开发实施过程

1. 前端实现(Vue2+TinyMCE)
  • 步骤1:集成TinyMCE编辑器

    // src/components/RichTextEditor.vue
    import tinymce from 'tinymce/tinymce';
    import 'tinymce/themes/silver';
    import 'tinymce/plugins/paste';
    import 'tinymce/plugins/image';
    
    export default {
      mounted() {
        tinymce.init({
          selector: '#news-editor',
          plugins: 'paste image',
          toolbar: 'paste | undo redo | styleselect | bold italic | alignleft aligncenter alignright',
          paste_preprocess: (plugin, args) => {
            if (isWordContent(args.content)) {
              this.parseWordContent(args.content).then(html => {
                args.content = html;
              });
            }
          },
          images_upload_handler: (blobInfo, success) => {
            this.uploadImage(blobInfo.blob()).then(url => success(url));
          }
        });
      },
      methods: {
        async parseWordContent(html) {
          // 替换Word生成的冗余标签(如)为HTML标准标签
          return html.replace(//g, '').replace(/> uploadImage(@RequestParam("file") MultipartFile file) {
            String objectName = "cms-news-images/" + UUID.randomUUID() + ".png";
            minioClient.putObject(
                PutObjectArgs.builder()
                    .bucket("xx-央企-cms")
                    .object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build()
            );
            String url = minioEndpoint + "/" + objectName;
            return ResponseEntity.ok(Map.of("url", url));
        }
    }
    
  • 步骤2:Word解析服务

    @Service
    public class WordParserService {
        public String parseDocxToHtml(MultipartFile file) throws IOException {
            XWPFDocument document = new XWPFDocument(file.getInputStream());
            StringBuilder html = new StringBuilder("");
            // 解析段落样式
            for (XWPFParagraph paragraph : document.getParagraphs()) {
                String style = paragraph.getStyleID();
                if ("Heading1".equals(style)) {
                    html.append("").append(paragraph.getText()).append("");
                } else {
                    html.append("").append(paragraph.getText()).append("");
                }
            }
            // 解析表格(简化示例)
            for (XWPFTable table : document.getTables()) {
                html.append("");
                for (XWPFTableRow row : table.getRows()) {
                    html.append("");
                    for (XWPFTableCell cell : row.getTableCells()) {
                        html.append("");
                    }
                    html.append("");
                }
                html.append("").append(cell.getText()).append("");
            }
            html.append("");
            return html.toString();
        }
    }
    
3. 信创环境适配
  • 跨平台兼容
    • Windows/macOS:使用Oracle JDK 1.8开发,打包时排除lib/amd64等平台相关目录。
    • 龙芯架构:替换Apache POILoongArch优化版,减少浮点运算依赖。
  • 数据库驱动
    • 达梦DM8:使用DmJdbcDriver18.jar,配置URL格式:jdbc:dm://host:5236/DBNAME
    • 人大金仓V9:启用rewriteBatchedStatements=true提升批量插入性能。

四、测试与部署

1. 测试用例
测试类型测试场景预期结果
功能测试导入含复杂表格、图片的Word文档表格边框/背景色保留,图片可点击放大
兼容性测试在银河麒麟(龙芯)+统信UOS下操作编辑器加载时间<5秒,无样式错乱
性能测试同时上传20个10MB的Word文件服务器CPU占用率≤80%,响应时间<8秒
安全测试上传含宏的Word文档(.docm)系统拦截并记录日志,返回403错误
2. 部署方案
  • 生产环境
    • 信创服务器:部署于银河麒麟V10(龙芯3C5000),使用Kubernetes 1.21管理容器。
    • 数据库高可用:达梦DM8采用DataWatch集群,人大金仓V9配置主从同步复制
  • 灾备方案
    • 每日凌晨3点执行全量备份,备份文件加密存储至集团NAS(华为OceanStor 5310)

五、技术支持与运维保障

  1. 服务承诺

    • 提供7×24小时企业微信/电话支持,紧急问题30分钟内响应,2小时内解决。
    • 每月出具《系统健康度报告》,包含性能趋势、安全漏洞扫描结果(使用麒麟系统自带工具)。
  2. 知识转移

    • 编制《信创环境下Vue2+TinyMCE开发手册》,包含:
      • 龙芯架构性能优化技巧(如减少DOM操作次数)。
      • 达梦/人大金仓SQL调优案例(如避免全表扫描)。
    • 对集团IT团队进行4次现场培训(每次3小时),覆盖日志分析、性能调优、灾备演练。

六、项目成果

  • 效率提升:新闻发布时间从平均2小时缩短至30分钟(含Word导入+审核)。
  • 信创认证:通过国家保密局工业信息安全发展研究中心双重认证(证书编号:CII-2023-XXXXXX)。
  • 成本节约:相比商业解决方案,节省授权费用约32万元/年。

附录

  • 完整代码库地址:[GitLab私有仓库链接]
  • 测试报告:[PDF文档链接]
  • 技术支持联系方式:企业微信群「XX央企CMS升级支持组」 | 紧急电话:010-XXXXXXX

记录人:XX央企信息技术部
日期:2025年XX月XX日

复制插件

WordPaster插件文件夹

安装jquery

npm install jquery

在组件中引入

  // 引入tinymce-vue
  import Editor from '@tinymce/tinymce-vue'
  import {WordPaster} from '../../static/WordPaster/js/w'
  import {zyOffice} from '../../static/zyOffice/js/o'
  import {zyCapture} from '../../static/zyCapture/z'

添加工具栏

//添加导入excel工具栏按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor).importExcel()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('excelimport', {
        text: '',
        tooltip: '导入Excel文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('excelimport', {
        text: '',
        tooltip: '导入Excel文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('excelimport', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加word转图片工具栏按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor);
      WordPaster.getInstance().importWordToImg()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('importwordtoimg', {
        text: '',
        tooltip: 'Word转图片',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('importwordtoimg', {
        text: '',
        tooltip: 'Word转图片',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('importwordtoimg', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加粘贴网络图片工具栏按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor);
      WordPaster.getInstance().UploadNetImg()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('netpaster', {
        text: '',
        tooltip: '网络图片一键上传',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('netpaster', {
        text: '',
        tooltip: '网络图片一键上传',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('netpaster', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加导入PDF按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor);
      WordPaster.getInstance().ImportPDF()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('pdfimport', {
        text: '',
        tooltip: '导入pdf文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('pdfimport', {
        text: '',
        tooltip: '导入pdf文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('pdfimport', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加导入PPT按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor);
      WordPaster.getInstance().importPPT()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('pptimport', {
        text: '',
        tooltip: '导入PowerPoint文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('pptimport', {
        text: '',
        tooltip: '导入PowerPoint文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('pptimport', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加导入WORD按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor).importWord()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('wordimport', {
        text: '',
        tooltip: '导入Word文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('wordimport', {
        text: '',
        tooltip: '导入Word文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('wordimport', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加WORD粘贴按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    var ico = "http://localhost:8080/static/WordPaster/plugin/word.png"
    function selectLocalImages(editor) {
      WordPaster.getInstance().SetEditor(editor).PasteManual()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('wordpaster', {
        text: '',
        tooltip: 'Word一键粘贴',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('wordpaster', {
        text: '',
        tooltip: 'Word一键粘贴',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('wordpaster', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

在线代码:

添加插件

// 插件
      plugins: {
          type: [String, Array],
          // default: 'advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars'
          default: 'autoresize code autolink autosave image imagetools paste preview table powertables'
      },

点击查看在线代码

初始化组件

// 初始化
WordPaster.getInstance({
    // 上传接口:http://www.ncmem.com/doc/view.aspx?id=d88b60a2b0204af1ba62fa66288203ed
    PostUrl: 'http://localhost:8891/upload.aspx',
    // 为图片地址增加域名:http://www.ncmem.com/doc/view.aspx?id=704cd302ebd346b486adf39cf4553936
    ImageUrl: 'http://localhost:8891{url}',
    // 设置文件字段名称:http://www.ncmem.com/doc/view.aspx?id=c3ad06c2ae31454cb418ceb2b8da7c45
    FileFieldName: 'file',
    // 提取图片地址:http://www.ncmem.com/doc/view.aspx?id=07e3f323d22d4571ad213441ab8530d1
    ImageMatch: ''
})

在页面中引入组件


功能演示

编辑器

在编辑器中增加功能按钮
TinyMCE编辑器界面

导入Word文档,支持doc,docx

粘贴Word和图片

导入Excel文档,支持xls,xlsx

粘贴Word和图片

粘贴Word

一键粘贴Word内容,自动上传Word中的图片,保留文字样式。
粘贴Word和图片

Word转图片

一键导入Word文件,并将Word文件转换成图片上传到服务器中。
导入Word转图片

导入PDF

一键导入PDF文件,并将PDF转换成图片上传到服务器中。
导入PDF转图片

导入PPT

一键导入PPT文件,并将PPT转换成图片上传到服务器中。
导入PPT转图片

上传网络图片

一键自动上传网络图片。
自动上传网络图片

下载示例

点击下载完整示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值