芯片制造企业如何解决CAD图纸粘贴到TinyMCE的矢量输出?
·
广东XX软件公司 - 客户单位CMS系统新闻模块PPT/PDF导入功能升级项目实施记录
一、项目背景与需求确认
-
客户单位需求:
- 核心功能:在CMS系统后台新闻模块中新增PPT/PDF一键导入功能,自动将文档转换为图片并上传至服务器,1:1还原原始内容样式(字体、颜色、动画帧、图表等)。
- 信创环境支持:
- 操作系统:Windows、macOS、统信UOS、中标麒麟、银河麒麟、龙芯(LoongArch架构)。
- 数据库:达梦DM8、人大金仓KingbaseES V8。
- 开发框架:前端Vue2-cli + TinyMCE编辑器,后端SpringBoot 2.7.x。
- 服务要求:提供7×24小时在线技术支持,故障响应时间≤30分钟,支持远程调试。
-
项目目标:
- 实现高保真文档转换,兼容国产化软硬件环境。
- 通过等保2.0三级测评,确保数据传输与存储安全性。
二、技术选型与产品评估
1. 候选方案分析
| 方案 | 可行性评估 |
|---|---|
| Apache POI + PDFBox | 优势:开源免费; |
| 风险:PPT动画/PDF复杂布局转换效果差,需大量手动优化。 | |
| 金山WPS云服务 | 优势:功能强大; |
| 风险:不提供产品源码,不支持私有部署。 | |
| 永中Office | 优势:功能强大; |
| 风险:不提供产品源码,不支持私有部署。 | |
| 腾讯文档 | 优势:功能强大; |
| 风险:不提供产品源码,不支持私有部署。 | |
| 钉钉文档 | 优势:功能强大; |
| 风险:不提供产品源码,不支持私有部署。 | |
| 飞书 | 优势:功能强大; |
| 风险:不提供产品源码,不支持私有部署。 | |
| 石墨文档 | 优势:功能强大; |
| 风险:不提供产品源码,成本比较高58万/年。 | |
| LibreOffice Online | 优势:支持PPT转图片; |
| 风险:信创环境部署复杂,龙芯架构兼容性未知。 | |
| 商业转换引擎(如Aspose) | 优势:高保真转换,支持信创认证; |
| 风险:成本高(约$2,400/年/核心)。 | |
| 开源转换引擎(如WordPaster) | 优势:高保真转换,支持信创认证,集成简单,使用简单,用户体验好,完全开放产品源代码(点击免费下载),能够满足政企100%自主安全可控需求; |
| 风险:终端需要安装控件。 | |
| 开源工具组合 | 决策方案: |
- PPT转换:Apache POI提取幻灯片 + Java AWT渲染为图片。
- PDF转换:PDFBox 2.0.27 + Ghostscript 9.50(信创版)。 |
关键决策点:
- 选用PDFBox+Ghostscript组合实现PDF转图片,通过调整DPI(300dpi)保证清晰度。
- PPT转换采用POI读取幻灯片对象树,结合Java 2D API自定义渲染引擎,解决动画帧丢失问题。
2. 技术栈确认
- 前端:
- Vue2-cli + TinyMCE 5.10(集成
filepicker插件支持多文件拖拽)。 - 使用
webpack-bundle-analyzer优化打包体积,兼容统信UOS浏览器。
- Vue2-cli + TinyMCE 5.10(集成
- 后端:
- SpringBoot 2.7.12 + Apache POI 5.2.3(PPT处理)。
- PDFBox 2.0.27 + Ghostscript 9.50(信创版,静默模式调用)。
- 异步处理:Spring Task + Redis实现转换任务队列。
- 信创适配:
- 龙芯平台:使用
LoongArch64架构的OpenJDK 11。 - 数据库驱动:达梦JDBC 8.1.1.193(支持JDBC 4.2)。
- 龙芯平台:使用
三、开发实施过程
1. 前端开发(Vue2 + TinyMCE)
-
步骤1:扩展TinyMCE文件上传组件。
// src/plugins/tinymce/init.js tinymce.init({ selector: '#news-editor', plugins: 'importcss filepicker', file_picker_callback: (callback, value, meta) => { if (meta.filetype === 'media') { const input = document.createElement('input'); input.type = 'file'; input.accept = '.pptx,.ppt,.pdf'; input.onchange = async (e) => { const file = e.target.files[0]; const formData = new FormData(); formData.append('file', file); // 显示加载动画 const loadingId = tinymce.activeEditor.windowManager.open({ title: '转换中', body: { type: 'panel', items: [{ type: 'htmlpanel', html: '正在生成图片...' }] } }); try { const res = await axios.post('/api/convert/document', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); callback(res.data.imageUrls.join('\n'), { text: file.name }); } finally { tinymce.activeEditor.windowManager.close(loadingId); } }; input.click(); } } }); -
步骤2:实现预览功能。
- 使用
pdf.js在前端渲染PDF首页缩略图,减少用户等待时间。
- 使用
2. 后端开发(SpringBoot)
-
步骤1:PPT转图片服务。
// src/main/java/com/xx/service/PptConverterService.java public List convertPptToImages(MultipartFile file) throws IOException { List imageUrls = new ArrayList<>(); try (XMLSlideShow ppt = new XMLSlideShow(file.getInputStream())) { for (int i = 0; i < ppt.getSlides().size(); i++) { // 自定义渲染逻辑(解决POI默认渲染丢失阴影问题) BufferedImage img = new BufferedImage( ppt.getPageSize().width, ppt.getPageSize().height, BufferedImage.TYPE_INT_RGB ); Graphics2D graphics = img.createGraphics(); ppt.getSlides().get(i).draw(graphics); graphics.dispose(); // 上传至OBS(华为云信创版) String url = obsClient.putObject( "cms-media", "ppt/" + UUID.randomUUID() + ".png", new ByteArrayInputStream(toByteArray(img, "png")) ); imageUrls.add(url); } } return imageUrls; } -
步骤2:PDF转图片服务(信创优化)。
# 调用Ghostscript命令(通过Runtime.exec) gs -sDEVICE=png16m -dNOPAUSE -dBATCH -r300 -sOutputFile=/tmp/output_%d.png input.pdf// 处理Ghostscript输出 public List convertPdfToImages(MultipartFile file) throws Exception { Path tempPdf = Files.createTempFile("pdf", ".pdf"); Files.write(tempPdf, file.getBytes()); // 执行Ghostscript命令(统信UOS路径适配) ProcessBuilder pb = new ProcessBuilder( "/opt/ghostscript-9.50/bin/gs", "-sDEVICE=png16m", "-dNOPAUSE", "-dBATCH", "-r300", "-sOutputFile=/tmp/output_%d.png", tempPdf.toString() ); pb.redirectErrorStream(true); Process process = pb.start(); process.waitFor(); // 上传图片至达梦数据库(存储为BLOB或URL) List urls = new ArrayList<>(); Files.list(Paths.get("/tmp")) .filter(p -> p.toString().contains("output_")) .forEach(p -> { String url = uploadToObs(p); urls.add(url); Files.deleteIfExists(p); // 清理临时文件 }); return urls; } -
步骤3:异步任务队列。
# application-prod.yml spring: task: scheduling: pool: size: 10 execution: thread-name-prefix: "doc-convert-task-" redis: host: 192.168.1.100 password: ${REDIS_PASS}
3. 信创环境兼容性处理
- 操作系统适配:
- 中标麒麟:修改
/etc/security/limits.conf,增加* soft nofile 65535防止文件描述符泄漏。 - 龙芯平台:使用
jlink定制JVM,移除无用模块减小体积。
- 中标麒麟:修改
- 数据库优化:
- 达梦数据库:为图片URL字段添加
GIN索引,加速全文检索。 - 人大金仓:配置
shared_buffers = 256MB以适应高并发转换任务。
- 达梦数据库:为图片URL字段添加
四、测试与验证
-
功能测试:
- PPT测试用例:
- 含100张幻灯片(含SmartArt图表)的PPTX文件,验证转换完整性。
- 测试嵌套动画效果(如出现+淡出组合动画)的图片序列生成。
- PDF测试用例:
- 扫描版PDF(图片型)与文本型PDF的OCR识别准确率对比。
- 复杂表格(跨页、合并单元格)的样式保留测试。
- PPT测试用例:
-
信创兼容性测试:
- 统信UOS + 龙芯3A5000:
- 使用
strace跟踪系统调用,确保无x86指令集依赖。
- 使用
- 银河麒麟V10:
- 验证
libgsoap库版本兼容性(需≥2.8.107)。
- 验证
- 统信UOS + 龙芯3A5000:
-
性能测试:
- 压力测试:模拟20个并发转换任务,CPU占用率≤70%。
- 长耗时测试:500页PDF转换耗时≤3分钟(300dpi下)。
五、技术支持与运维方案
-
7×24小时服务保障:
- 部署Prometheus + Grafana监控系统,实时告警转换失败任务。
- 建立信创专家坐席,提供远程桌面支持(TeamViewer信创版)。
-
知识转移:
- 提供《Ghostscript信创部署指南》《PPT自定义渲染开发手册》。
- 录制龙芯平台调试教程视频,覆盖JVM参数调优等场景。
六、项目交付成果
-
功能模块:
- 新闻编辑器新增“PPT/PDF导入”按钮,支持多文件批量转换。
- 图片自动压缩(长边≤2000px),支持WebP格式以节省带宽。
-
文档清单:
- 《信创环境适配报告》《API接口规范》《性能测试报告》。
- 自动化测试脚本库(含300+测试用例)。
-
验收标准:
- 通过国家电子计算机质量监督检验中心的兼容性认证。
- 客户满意度评分≥9.5分(满分10分)。
项目负责人签字:_________________
客户单位代表签字:_________________
日期:2025年XX月XX日
备注:本项目代码已通过华为云DevCloud信创代码扫描,无中高危漏洞。
复制插件

安装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: ''
})
在页面中引入组件
功能演示
编辑器
在编辑器中增加功能按钮
导入Word文档,支持doc,docx

导入Excel文档,支持xls,xlsx

粘贴Word
一键粘贴Word内容,自动上传Word中的图片,保留文字样式。
Word转图片
一键导入Word文件,并将Word文件转换成图片上传到服务器中。
导入PDF
一键导入PDF文件,并将PDF转换成图片上传到服务器中。
导入PPT
一键导入PPT文件,并将PPT转换成图片上传到服务器中。
上传网络图片
一键自动上传网络图片。
下载示例
更多推荐


所有评论(0)