农业集团信息管理系统富文本增强功能开发全记录

一、需求分析与技术选型

作为湖南某农业集团技术负责人,近期接到客户核心需求:在现有信息管理系统的后台文章发布模块中新增两大功能:

  1. 智能粘贴功能:支持从Word/微信公众号直接粘贴内容,图片自动上传至专用存储服务器(未来可平滑迁移至多云对象存储)
  2. 文档导入功能:支持Word/Excel/PPT/PDF全格式导入,完整保留样式与图片

技术栈现状:

  • 前端:Vue2-CLI + UEditor(百度开源版)
  • 后端:SpringBoot 2.7.x + Oracle 19c
  • 部署环境:内网私有化部署(需支持离线环境)
  • 存储方案:初期采用文件系统存储,预留对象存储接口

二、技术调研与方案评估

1. 富文本编辑器扩展方案

UEditor深度定制

  • 发现官方已停止维护,但社区活跃度较高
  • 核心需求需解决:
    • 保留Word样式(需解析Office Open XML)
    • 图片二进制流处理(避免Base64污染DOM)
    • 微信公众号特殊格式兼容

替代方案对比

方案 优势 风险
扩展UEditor 现有系统兼容性好,迁移成本低 需自行处理复杂文档解析
迁移wangEditor 现代架构,支持插件机制 需重构现有编辑器集成
引入TinyMCE 企业版支持Office文档 商业授权成本高
泽优WordPaster 完全开源(下载源码
支持任何系统
支持任何开发语言
支持主流编辑器
免费技术支持(QQ群:223813913) 需要终端安全控件

最终决策:基于UEditor进行二次开发,通过插件机制实现功能扩展

2. 文档解析技术选型

Word/PPT解析

  • 采用Apache POI升级版(poi-tl 3.12+)
  • 关键特性:
    // 示例:提取Word中的图片与样式
    XWPFDocument doc = new XWPFDocument(inputStream);
    doc.getParagraphs().forEach(para -> {
        // 提取段落样式
        CTR ctr = para.getCTR();
        // 提取嵌入图片
        doc.getAllPictures().forEach(pic -> {
            byte[] imageData = pic.getData();
            // 处理二进制流...
        });
    });
    

PDF解析

  • 选用PDFBox 2.0.27(Apache许可)
  • 实现要点:
    • 文本流提取时保留位置信息
    • 图片提取需处理CCITT压缩格式

Excel解析

  • 使用EasyExcel 3.3.2(阿里开源)
  • 特殊处理:
    • 合并单元格样式映射
    • 图表对象提取(暂不支持)

3. 存储架构设计

初期方案

/storage
  /images
    /doc
      {docId}/
        {md5}.jpg
  /temp

对象存储接口

public interface CloudStorageService {
    String upload(byte[] data, String ext, String module);
    void delete(String key);
    // 实现类包括AliyunOSSImpl, HuaweiOBSImpl等
}

三、核心功能实现

1. 智能粘贴功能开发

前端实现

// UEditor插件扩展
UE.plugins['smartpaste'] = function() {
    const me = this;
    
    // 监听粘贴事件
    me.addListener('beforepaste', function(type, html) {
        // 微信公众号内容特殊处理
        if (isWechatContent(html)) {
            return processWechatContent(html);
        }
        
        // Word内容处理
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const images = doc.querySelectorAll('img[src^="blob:"]');
        
        // 批量上传图片
        return Promise.all(
            Array.from(images).map(img => {
                return fetch(img.src).then(res => res.blob())
                    .then(blob => uploadImage(blob));
            })
        ).then(urls => {
            // 替换图片URL
            let i = 0;
            return html.replace(/]+src="blob:[^"]+"/g, () => {
                return ` handleImage(
            @RequestParam("file") MultipartFile file,
            @RequestParam(value = "module", defaultValue = "doc") String module) {
        
        // 生成唯一文件名
        String filename = UUID.randomUUID() + 
            StringUtils.getExtension(file.getOriginalFilename());
        
        // 存储二进制数据
        String path = storageService.store(
            file.getBytes(), 
            filename, 
            module
        );
        
        return ResponseEntity.ok(new UploadResult(path));
    }
}

2. 文档导入功能实现

统一处理入口

@Service
public class DocumentImportService {
    
    public ImportResult importDocument(MultipartFile file) throws IOException {
        String ext = StringUtils.getExtension(file.getOriginalFilename());
        
        switch (ext.toLowerCase()) {
            case "docx":
            case "doc":
                return new WordImporter().importDoc(file);
            case "xlsx":
            case "xls":
                return new ExcelImporter().importXls(file);
            case "pptx":
            case "ppt":
                return new PptImporter().importPpt(file);
            case "pdf":
                return new PdfImporter().importPdf(file);
            default:
                throw new IllegalArgumentException("不支持的文档类型");
        }
    }
}

Word导入核心逻辑

public class WordImporter {
    
    public ImportResult importDoc(MultipartFile file) throws IOException {
        try (XWPFDocument doc = new XWPFDocument(file.getInputStream())) {
            ImportResult result = new ImportResult();
            
            // 处理段落
            doc.getParagraphs().forEach(para -> {
                String text = para.getText();
                if (StringUtils.isNotBlank(text)) {
                    result.addContent(text);
                }
                
                // 处理段落样式
                CTR ctr = para.getCTR();
                if (ctr.isSetRPr()) {
                    CTRPr rpr = ctr.getRPr();
                    // 解析字体、颜色等样式...
                }
            });
            
            // 处理图片(异步上传)
            List pictures = doc.getAllPictures();
            pictures.forEach(pic -> {
                String url = uploadService.upload(
                    pic.getData(), 
                    pic.suggestFileExtension(), 
                    "doc"
                );
                result.addImage(url);
            });
            
            return result;
        }
    }
}

四、关键问题解决

1. 样式保留方案

字体处理

  • 建立字体映射表(解决Windows特有字体问题)
private static final Map FONT_MAP = Map.of(
    "Calibri", "Arial",
    "Microsoft YaHei", "SimHei"
);

颜色转换

// 前端样式转换
function convertColor(officeColor) {
    // 处理Word中的RGB颜色(如#FF0000)
    if (/^#[0-9A-F]{6}$/i.test(officeColor)) {
        return officeColor;
    }
    // 处理RGB值(如rgb(255,0,0))
    const match = officeColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
    if (match) {
        return `#${match.slice(1).map(c => 
            parseInt(c).toString(16).padStart(2, '0')
        ).join('')}`;
    }
    return '#000000';
}

2. 性能优化措施

图片处理

  • 后端启用异步上传队列(RabbitMQ实现)
  • 前端实现图片懒加载

大文件处理

  • 分片上传(基于WebUploader组件)
  • 导入进度显示(WebSocket实时推送)

3. 安全加固方案

文件校验

public class FileValidator {
    
    private static final Set ALLOWED_TYPES = Set.of(
        "doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"
    );
    
    public static void validate(MultipartFile file) {
        // 文件类型校验
        String ext = StringUtils.getExtension(file.getOriginalFilename());
        if (!ALLOWED_TYPES.contains(ext.toLowerCase())) {
            throw new SecurityException("非法文件类型");
        }
        
        // 文件内容校验(魔数检测)
        byte[] header = new byte[8];
        try (InputStream is = file.getInputStream()) {
            is.read(header);
            // 检测PDF魔数(%PDF-)
            if (ext.equals("pdf") && !new String(header).startsWith("%PDF-")) {
                throw new SecurityException("无效的PDF文件");
            }
            // 其他格式检测...
        } catch (IOException e) {
            throw new RuntimeException("文件读取失败", e);
        }
    }
}

五、部署与测试

1. 私有化部署方案

Docker化部署

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/doc-manager-*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

Oracle配置

# application.properties
spring.datasource.url=jdbc:oracle:thin:@//192.168.1.100:1521/ORCL
spring.datasource.username=DOC_MANAGER
spring.datasource.password=ENC(加密密码)
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver

2. 测试用例设计

核心场景测试

测试类型 测试用例 预期结果
粘贴功能 从Word复制带图片的内容粘贴 图片正确上传,样式保留
导入功能 导入复杂格式的PPT 幻灯片布局正确转换
性能测试 导入50MB的PDF文件 5分钟内完成,内存不溢出
安全测试 上传恶意脚本文件 系统拒绝并记录日志

六、后续优化方向

  1. 多云存储适配:开发阿里云/华为云等对象存储适配器
  2. 移动端适配:优化H5端的粘贴体验
  3. AI增强:集成OCR识别实现图片文字提取
  4. 版本控制:增加文档历史版本管理功能

整个开发周期历时6周,通过深度定制UEditor和精心设计后端处理流程,成功实现了客户要求的复杂文档处理功能。系统在内网环境稳定运行3个月后,已开始规划向公有云环境的迁移方案。

复制插件目录

WordPaster插件目录

引入插件文件


	
	UEditor 1.4.3.3示例
	
    
	
	
    
    
    
    
    
    
	
    

注意:不要重复引入jquery,如果您的项目已经引入了jq,则不用再引入jq-1.4
image

在工具栏中增加插件按钮

//工具栏上的所有的功能按钮和下拉框,可以在new编辑器的实例时选择自己需要的重新定义
    toolbars: [
      [
        "fullscreen",
        "source",
        "|",
        "zycapture",
        "|",
        "wordpaster","importwordtoimg","netpaster","wordimport","excelimport","pptimport","pdfimport",
        "|",
        "importword","exportword","importpdf"
      ]
    ]

初始化控件

image

        var pos = window.location.href.lastIndexOf("/");
        var api = [
            window.location.href.substr(0, pos + 1),
            "asp/upload.asp"
        ].join("");
        WordPaster.getInstance({
			//上传接口:http://www.ncmem.com/doc/view.aspx?id=d88b60a2b0204af1ba62fa66288203ed
            PostUrl: api,
			//为图片地址增加域名:http://www.ncmem.com/doc/view.aspx?id=704cd302ebd346b486adf39cf4553936
            ImageUrl: "",
            //设置文件字段名称:http://www.ncmem.com/doc/view.aspx?id=c3ad06c2ae31454cb418ceb2b8da7c45
            FileFieldName: "file",
            //提取图片地址:http://www.ncmem.com/doc/view.aspx?id=07e3f323d22d4571ad213441ab8530d1
            ImageMatch: ''			
        });//加载控件

注意

如果接口字段名称不是file,请配置FileFieldName。ueditor接口中使用的upfile字段
image
点击查看详细教程

配置ImageMatch

匹配图片地址,如果服务器返回的是JSON则需要通过正则匹配

ImageMatch: '',

点击参考链接

配置ImageUrl

为图片地址增加域名,如果服务器返回的图片地址是相对路径,可通过此属性添加自定义域名。

ImageUrl: "",

点击查看详细教程

配置SESSION

如果接口有权限验证(登陆验证,SESSION验证),请配置COOKIE。或取消权限验证。
参考:http://www.ncmem.com/doc/view.aspx?id=8602DDBF62374D189725BF17367125F3

效果

编辑器界面

image

导入Word文档,支持doc,docx

粘贴Word和图片

导入Excel文档,支持xls,xlsx

粘贴Word和图片

粘贴Word

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

Word转图片

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

导入PDF

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

导入PPT

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

上传网络图片

自动上传网络图片

下载示例

点击下载完整示例

Logo

更多推荐