项目需求分析与技术选型评估记录
日期:2023年X月X日
项目名称:国企政务网站后台管理系统升级(Word/微信内容集成)
负责人:XXX(黄山某国企项目负责人)
一、需求梳理与核心指标
-
功能需求:
- Word粘贴:从Office/WPS复制内容到TinyMCE编辑器,自动上传图片至华为云OBS(二进制存储),保留表格/字体/颜色等样式;
- 微信内容粘贴:解析公众号文章HTML,自动下载图片并上传至OBS;
- 文档导入:支持Word/Excel/PPT/PDF导入,保留图片和样式;
- 信创兼容:覆盖国产操作系统(麒麟/统信UOS)和CPU架构(龙芯/飞腾/鲲鹏)。
-
技术约束:
- 前端:Vue2 + TinyMCE 5.x;
- 后端:SpringBoot 2.7 + MySQL 8.0;
- 存储:华为云OBS(需兼容阿里云/腾讯云等对象存储);
- 授权模式:一次性买断(预算≤50万元)。
二、技术选型评估
1. 编辑器插件方案对比
| 方案 | 优势 | 风险点 | 信创兼容性 | 成本 |
|---|---|---|---|---|
| TinyMCE官方插件 | 原生集成,支持Word粘贴(paste插件),但需二次开发图片上传逻辑 | 微信内容解析需自定义,文档导入需额外插件(如powerpaste,但授权费高) | 部分支持 | 高(年费) |
| 第三方SDK(如UEditor) | 功能全(支持文档导入),但基于jQuery,与Vue2集成复杂 | 信创环境测试不充分,可能存在兼容性问题 | 中 | 中(买断) |
| 开源方案(wangeditor + 自定义扩展) | 完全可控,可针对信创环境优化 | 开发周期长,需自行实现Word/微信内容解析 | 高 | 低(自研) |
| 商业组件(如Froala + 信创适配版) | 功能完善,提供买断授权,支持信创环境 | 价格接近预算上限,需确认龙芯/MIPS架构支持 | 高 | 高(买断) |
| 开源组件(如WordPaster + 信创适配版) | 功能完善 | |||
| 开放源码(下载源码) | ||||
| 支持信创环境 | ||||
| 支持多种编辑器 | ||||
| 支持多种开发语言 | 需要终端安装插件 | 高 | 低(买断) |
最终选择:
- 核心方案:WordPaster + 源码版(兼顾功能与成本控制);
- 辅助工具:
- 使用
mammoth.js解析Word文档(纯前端解析,避免后端依赖); - 使用
html-docx-js实现导出功能; - 微信内容解析通过正则匹配图片URL,结合
axios下载并上传至OBS。
- 使用
三、开发过程记录
1. 前端实现(Vue2 + TinyMCE)
关键代码:自定义插件集成
// src/components/Editor.vue
import tinymce from 'tinymce/tinymce';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/table';
import 'tinymce/themes/silver';
export default {
mounted() {
tinymce.init({
selector: '#editor',
plugins: 'paste table advlist',
toolbar: 'paste | formatselect | bold italic | alignleft aligncenter alignright',
paste_data_images: true, // 允许粘贴图片(需配合自定义处理)
paste_preprocess: (plugin, args) => {
// 拦截粘贴内容,处理微信图片和Word样式
const html = args.content;
this.processWeChatImages(html).then(processedHtml => {
args.content = processedHtml;
});
},
images_upload_handler: (blobInfo, success) => {
// 上传图片至华为云OBS
const formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
axios.post('/api/upload/obs', formData, {
headers: { 'Authorization': 'Bearer ' + localStorage.token }
}).then(res => {
success(res.data.url); // 返回OBS访问URL
});
}
});
},
methods: {
async processWeChatImages(html) {
// 正则匹配微信图片URL并替换为OBS地址
const imgRegex = /]+src="([^"]+)"[^>]*>/g;
let match;
while ((match = imgRegex.exec(html)) !== null) {
const imgUrl = match[1];
if (imgUrl.includes('mp.weixin.qq.com')) {
const blob = await this.downloadImage(imgUrl);
const obsUrl = await this.uploadToOBS(blob);
html = html.replace(imgUrl, obsUrl);
}
}
return html;
},
downloadImage(url) {
return axios.get(url, { responseType: 'blob' }).then(res => res.data);
}
}
};
2. 后端实现(SpringBoot + OBS SDK)
关键代码:图片上传接口
// src/main/java/com/example/controller/UploadController.java
@RestController
@RequestMapping("/api/upload")
public class UploadController {
@Value("${obs.endpoint}")
private String obsEndpoint;
@Value("${obs.accessKey}")
private String accessKey;
@PostMapping("/obs")
public ResponseEntity> uploadToOBS(@RequestParam("file") MultipartFile file) {
String objectKey = "images/" + UUID.randomUUID() + "_" + file.getOriginalFilename();
// 初始化OBS客户端(华为云SDK)
ObsClient obsClient = new ObsClient(accessKey, "secretKey", obsEndpoint);
obsClient.putObject("your-bucket", objectKey, file.getInputStream());
Map result = new HashMap<>();
result.put("url", "https://your-bucket.obs.cn-east-3.myhuaweicloud.com/" + objectKey);
return ResponseEntity.ok(result);
}
}
四、信创环境适配方案
-
操作系统兼容:
- 使用
Electron打包前端为桌面应用,通过Wine兼容麒麟/统信UOS; - 后端部署于
CentOS 7/8(x86/ARM)或Kylin V10(龙芯)。
- 使用
-
CPU架构优化:
- 编译时指定目标架构(如
-march=loongarch64); - 使用
OpenJDK的信创版本(如麒麟JDK)。
- 编译时指定目标架构(如
-
测试验证:
- 通过
QEMU模拟龙芯/飞腾环境; - 使用
信创浏览器(如360安全浏览器信创版)测试前端功能。
- 通过
五、预算与授权模式
-
成本构成:
- TinyMCE商业版买断:约15万元(含信创适配支持);
- 华为云OBS存储:按需付费(预估每年2万元);
- 自研开发成本:约20万元(含测试与文档)。
-
授权协商:
- 与TinyMCE厂商签订永久授权协议,明确支持未来5年信创环境更新;
- 要求提供源码级二次开发权限,避免供应商锁定。
六、风险与应对
-
微信内容解析稳定性:
- 风险:公众号HTML结构变更导致解析失败;
- 应对:定期更新正则表达式,提供手动修正入口。
-
信创环境性能:
- 风险:龙芯MIPS架构下JavaScript执行效率低;
- 应对:优化图片处理逻辑,减少前端计算量。
下一步计划:
- 完成POC(概念验证)环境搭建,重点测试龙芯+统信UOS组合;
- 与华为云团队确认OBS SDK的国产化支持情况;
- 启动商务谈判,锁定TinyMCE买断价格。
记录人:XXX
日期:2023年X月X日
复制插件

安装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转换成图片上传到服务器中。

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

955

被折叠的 条评论
为什么被折叠?



