一个北京JAVA程序员的CMS官网逆袭之路:Word导入插件开发全记录
前言:从"要饭"到"躺赚"的奇妙旅程
各位老铁们好!我是老王,一个在北京中关村敲代码的"码农"。最近接了个CMS企业官网的外包项目,客户爸爸提出了个"变态"需求:要在KindEditor里实现Word/Excel/PPT/PDF一键导入,还要保留所有样式和公式!
这需求一出来,我差点把键盘摔了——这特么是要让我用JSP实现Office全家桶啊?但看到客户那期待的眼神,再摸摸自己瘪瘪的钱包,我咬咬牙:干!
第一章:需求分析——客户爸爸的"五彩斑斓黑"
客户的核心需求就三个字:简单、强、省钱
- 要简单:插件形式,工具栏加个按钮就能用
- 要强大:支持Office全家桶+公众号内容导入,公式图片样式全保留
- 要省钱:预算680元(这价格在北京连顿火锅都吃不上)
最坑的是:网上开源方案都拉胯!emz/wmz公式图片不支持,LaTeX公式显示成狗屎,图片还得手动上传…
第二章:技术选型——穷人的智慧
前端部分
- 框架:Vue3 CLI(客户指定)
- 编辑器:KindEditor4(老古董了,但客户在用)
- 插件方案:改造KindEditor的paste插件
后端部分
- 语言:JAVA(JSP,客户遗留系统)
- 工具:Eclipse JEE(比IDEA卡,但免费)
- 存储:阿里云OSS(客户已有)
关键技术点
- Office文档解析:用Apache POI处理Word/Excel,PPT用Tika,PDF用PDFBox
- 公式处理:用MathJax处理LaTeX转MathML
- 图片处理:用Thumbnailator压缩图片,自动上传OSS
- 跨终端显示:MathML+SVG方案
第三章:代码实现——从0到1的奇迹
前端插件代码(Vue3版)
// src/plugins/kindEditorWordImport.js
import KindEditor from 'kindeditor'
export default {
install(app) {
// 扩展KindEditor
KindEditor.plugin('wordimport', function(K) {
var self = this, name = 'wordimport';
// 添加工具栏按钮
self.clickToolbar(name, function() {
// 调用文件选择对话框
const input = document.createElement('input')
input.type = 'file'
input.accept = '.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.html,.txt'
input.onchange = async (e) => {
const file = e.target.files[0]
if (!file) return
// 显示加载动画
self.loading(1)
try {
// 调用后端API处理文件
const formData = new FormData()
formData.append('file', file)
const res = await fetch('/api/office/import', {
method: 'POST',
body: formData
})
const { html, error } = await res.json()
if (error) {
alert('导入失败: ' + error)
return
}
// 插入处理后的HTML
self.insertHtml(html)
} catch (e) {
console.error('导入错误:', e)
alert('导入过程中发生错误')
} finally {
self.loading(0)
}
}
input.click()
})
// 添加按钮到工具栏
self.afterSetToolbar(function() {
if (!self.toolbar[name]) {
self.toolbar.push({
name: name,
cmd: name,
title: '导入Word/Excel/PPT/PDF'
})
}
})
})
}
}
后端JSP处理代码
<%-- /api/office/import.jsp --%>
<%@ page import="org.apache.poi.*, org.apache.poi.xwpf.*, org.apache.commons.io.*" %>
<%@ page import="com.aliyun.oss.*, com.aliyun.oss.model.*" %>
<%@ page import="org.jsoup.*, org.jsoup.nodes.*" %>
<%@ page import="java.util.*, java.io.*, java.util.regex.*" %>
<%
// 配置信息
String endpoint = "your-oss-endpoint";
String accessKeyId = "your-access-key";
String accessKeySecret = "your-secret-key";
String bucketName = "your-bucket";
String ossFolder = "uploads/office/";
// 初始化OSS客户端
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 获取上传的文件
Part filePart = request.getPart("file");
String fileName = filePart.getSubmittedFileName();
String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
// 根据文件类型处理
String htmlContent = "";
if (fileExt.equals("docx") || fileExt.equals("doc")) {
htmlContent = processWord(filePart.getInputStream());
} else if (fileExt.equals("xlsx") || fileExt.equals("xls")) {
htmlContent = processExcel(filePart.getInputStream());
} else if (fileExt.equals("pptx") || fileExt.equals("ppt")) {
htmlContent = processPPT(filePart.getInputStream());
} else if (fileExt.equals("pdf")) {
htmlContent = processPDF(filePart.getInputStream());
} else {
throw new Exception("不支持的文件类型");
}
// 处理图片上传到OSS
htmlContent = uploadImagesToOSS(htmlContent, ossClient, bucketName, ossFolder);
// 处理LaTeX公式转MathML
htmlContent = convertLatexToMathML(htmlContent);
// 返回结果
response.setContentType("application/json");
response.getWriter().write("{\"html\":\"" + escapeJson(htmlContent) + "\"}");
} catch (Exception e) {
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"" + escapeJson(e.getMessage()) + "\"}");
} finally {
ossClient.shutdown();
}
// 处理Word文档
String processWord(InputStream is) throws Exception {
XWPFDocument doc = new XWPFDocument(is);
XWPFWordConverter converter = new XWPFWordConverter(doc);
StringWriter writer = new StringWriter();
converter.process(writer);
return writer.toString();
}
// 处理Excel文档(简化版)
String processExcel(InputStream is) throws Exception {
// 实际项目中需要更复杂的处理
return "Excel内容处理(简化示例)";
}
// 处理PPT文档(简化版)
String processPPT(InputStream is) throws Exception {
// 实际项目中需要更复杂的处理
return "PPT内容处理(简化示例)";
}
// 处理PDF文档
String processPDF(InputStream is) throws Exception {
// 使用PDFBox或其他库处理
// 实际项目中需要更完整的实现
return "PDF内容处理(简化示例)";
}
// 图片上传到OSS
String uploadImagesToOSS(String html, OSS ossClient, String bucketName, String ossFolder) {
Document doc = Jsoup.parse(html);
Elements images = doc.select("img");
for (Element img : images) {
String src = img.attr("src");
if (src.startsWith("data:image/")) {
// 提取base64数据
String base64Data = src.substring(src.indexOf(",") + 1);
byte[] imageBytes = Base64.getDecoder().decode(base64Data);
// 生成唯一文件名
String fileName = UUID.randomUUID().toString() + ".png";
String ossPath = ossFolder + fileName;
// 上传到OSS
ossClient.putObject(new PutObjectRequest(bucketName, ossPath, new ByteArrayInputStream(imageBytes)));
// 替换为OSS URL
String ossUrl = "https://" + bucketName + "." + endpoint.replace("https://", "") + "/" + ossPath;
img.attr("src", ossUrl);
}
}
return doc.body().html();
}
// LaTeX转MathML(简化版)
String convertLatexToMathML(String html) {
// 实际项目中应该使用MathJax或KaTeX的服务器端转换
// 这里只是示例,实际需要更完整的实现
return html.replaceAll("\\$(\\S+?)\\$", "$1");
}
// JSON转义
String escapeJson(String str) {
return str.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r");
}
%>
第四章:部署与测试——从崩溃到起飞
部署步骤
- 把前端插件放到Vue项目的plugins目录
- 在main.js中引入插件
- 把JSP文件放到Web应用的api/office目录
- 配置阿里云OSS参数
- 重启应用服务器
测试用例
// 测试代码(在Vue组件中)
import { ref, onMounted } from 'vue'
import KindEditor from 'kindeditor'
import 'kindeditor/themes/default/default.css'
import WordImportPlugin from '@/plugins/kindEditorWordImport'
export default {
setup() {
const editorRef = ref(null)
onMounted(() => {
const app = { $el: document.getElementById('editor-container') }
WordImportPlugin.install(app)
editorRef.value = KindEditor.create('#editor-container', {
plugins: ['wordimport'],
items: [
'wordimport', 'fontname', 'fontsize', '|',
'forecolor', 'hilitecolor', 'bold', 'italic', 'underline'
]
})
})
return { editorRef }
}
}
第五章:赚钱攻略——从码农到斜杠青年
QQ群运营秘籍
- 新人福利:加群送1-99元红包(实际成本0.5元/人,用支付宝红包)
- 内容为王:
- 每天分享一个技术小技巧
- 每周一次项目复盘直播
- 每月一次外包项目接单培训
- 裂变机制:
- 推荐新人得20%提成
- 群成员接单成功,推荐人得5%奖励
- 每月群内接单冠军奖励200元
推广话术
"兄弟们,还在为找不到外包项目发愁吗?还在为技术提升慢苦恼吗?加入我们的QQ群:223813913,这里有:
- 实时更新的外包项目信息
- 大厂内推机会
- 独家技术教程
- 每天红包雨
- 推荐奖励拿到手软
现在加入还送《JAVA外包接单秘籍》电子书,前100名免费咨询技术方案!"
第六章:未来展望——从680到680万
这个项目虽然预算只有680元,但背后隐藏着巨大的商机:
- 标准化产品:把插件打包成可销售的产品
- SaaS服务:提供在线文档转换服务
- 企业定制:为政府、学校等机构提供定制解决方案
目前我已经通过这个方案接了3个外包项目,总收入突破2万元!而这一切,都始于那个"变态"的需求和680元的预算。
结语:代码改变命运
兄弟们,在这个内卷的时代,我们码农不能只会写代码,更要学会:
- 把技术变成产品
- 把产品变成流量
- 把流量变成现金
加入我们的QQ群:223813913,让我们一起:
- 交流技术
- 分享项目
- 赚外快
- 实现财务自由
记住:在互联网时代,最贵的不是代码,而是连接人的能力!
(PS:群文件里有完整源代码和部署文档,新人还有惊喜福利哦~)
上传工具栏插件文件夹
上传插件文件夹
控件初始化
在head中引入组件文件
注意,不要重复引入jquery,如果您的页面已经引入了jquery这里就不要再引入jquery 1.4了。
WordPaster For KindEditor-4.x
# 初始化组件
WordPaster.getInstance({
ui:{render:"wdpst"}//目标容器,一般为div
});
设置快捷键
将插件添加到工具栏,并挂载KindEditor的Ctrl+V快捷键事件
var editor;
KindEditor.ready(function (K)
{
editor = K.create('#content1'
,
{ items: [
'wordpaster','importwordtoimg','netpaster','wordimport','excelimport','pptimport','pdfimport', '|',
'importword','exportword','importpdf','|'
]
, afterCreate: function ()
{
WordPaster.getInstance().SetEditor(this);
var self = this;
//自定义 Ctrl + V 事件。
KindEditor.ctrl(self.edit.doc, 'V', function () { WordPaster.getInstance().Paste(); });
}
});
});
注意
1.如果接口字段名称不是file,请配置FileFieldName。
2.如果接口返回JSON,请配置ImageMatch
3.如果接口返回的图片地址没有域名,请配置ImageUrl
整合效果
效果
编辑器界面
导入Word文档,支持doc,docx
导入Excel文档,支持xls,xlsx
粘贴Word
一键粘贴Word内容,自动上传Word中的图片,保留文字样式。
Word转图片
一键导入Word文件,并将Word文件转换成图片上传到服务器中。
导入PDF
一键导入PDF文件,并将PDF转换成图片上传到服务器中。
导入PPT
一键导入PPT文件,并将PPT转换成图片上传到服务器中。