国企项目Word和微信公众号内容导入解决方案

项目背景与需求分析

作为福建某国企的项目负责人,我们近期在企业网站后台管理系统升级中遇到了一项关键需求:需要为文章发布模块增加Word粘贴、Word文档导入以及微信公众号内容粘贴功能。这些功能要求集成到现有的百度UEditor富文本编辑器中,作为插件形式提供,不影响现有系统功能和业务流程。

核心功能需求

  1. Word粘贴功能:支持从Word复制内容后直接粘贴到网站编辑器,图片自动上传至服务器
  2. Word文档导入:支持导入Word/Excel/PPT/PDF文档,保留原始样式和内容
  3. 微信公众号粘贴:自动下载公众号文章图片并上传至服务器
  4. 云存储支持:当前使用单据存储服务器,未来可升级到主流云存储服务

技术环境要求

  • 前端框架:兼容Vue2/Vue3/React
  • 编辑器:百度UEditor
  • 后端框架:JSP
  • 开发工具:Eclipse/MyEclipse/IntelliJ IDEA
  • 数据库:MySQL
  • 服务器:阿里云ECS/CentOS/RedHat
  • 信创环境:支持国产操作系统和浏览器
  • CPU架构:x86/ARM/MIPS/LoongArch

解决方案设计

整体架构

我们采用"前端插件+后端服务"的架构模式,确保与现有系统无缝集成:

  1. 前端插件:基于UEditor扩展开发,提供统一的API接口
  2. 后端服务:独立部署的文档处理服务,提供RESTful API
  3. 存储适配层:支持多种存储后端,便于未来扩展

前端实现方案

UEditor插件实现
// word-import-plugin.js
UE.plugins.wordImport = function() {
    const editor = this;
    
    // 添加工具栏按钮
    editor.addListener('ready', function() {
        editor.ui.addButton('wordImport', {
            title: '导入Word/微信',
            cssRules: 'background-position: -320px -20px;',
            onclick: function() {
                showImportDialog(editor);
            }
        });
    });
    
    function showImportDialog(editor) {
        // 显示导入对话框逻辑
        const dialog = new UE.ui.Dialog({
            iframeUrl: editor.getOpt('wordImportDialogUrl') || 'word-import.html',
            editor: editor,
            name: 'wordImport',
            title: '导入Word/微信内容',
            cssRules: 'width:800px;height:500px;',
            buttons: [
                {
                    className: 'edui-okbutton',
                    label: '确定',
                    onclick: function() {
                        dialog.close(true);
                    }
                },
                {
                    className: 'edui-cancelbutton',
                    label: '取消',
                    onclick: function() {
                        dialog.close(false);
                    }
                }
            ]
        });
        
        dialog.render();
        dialog.open();
    }
    
    // 处理Word粘贴
    editor.addListener('beforePaste', function(type, html) {
        if (isWordContent(html)) {
            return processWordPaste(html);
        }
        return html;
    });
    
    function isWordContent(html) {
        // 检测是否为Word内容
        return html.includes('mso-') || 
               html.includes('urn:schemas-microsoft-com:office:office');
    }
    
    async function processWordPaste(html) {
        try {
            const response = await fetch('/api/word/process', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ content: html })
            });
            
            const result = await response.json();
            if (result.success) {
                return result.data.processedHtml;
            }
            return html;
        } catch (error) {
            console.error('Word处理失败:', error);
            return html;
        }
    }
};
微信内容处理组件
// wechat-import.js
export default {
    methods: {
        async fetchWechatContent(url) {
            try {
                const response = await this.$http.post('/api/wechat/fetch', { url });
                if (response.data.success) {
                    return response.data.content;
                }
                throw new Error(response.data.message || '获取微信内容失败');
            } catch (error) {
                console.error('获取微信内容失败:', error);
                throw error;
            }
        },
        
        async uploadImages(html) {
            const imageUrls = this.extractImageUrls(html);
            const uploadPromises = imageUrls.map(url => 
                this.uploadImage(url).catch(e => {
                    console.error(`图片上传失败: ${url}`, e);
                    return null;
                })
            );
            
            const results = await Promise.all(uploadPromises);
            return this.replaceImageUrls(html, results.filter(Boolean));
        },
        
        extractImageUrls(html) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            return Array.from(doc.querySelectorAll('img'))
                .map(img => img.src)
                .filter(src => src.startsWith('http'));
        },
        
        async uploadImage(url) {
            const response = await this.$http.post('/api/image/upload', { url });
            if (response.data.success) {
                return {
                    originalUrl: url,
                    newUrl: response.data.url
                };
            }
            throw new Error(response.data.message || '图片上传失败');
        },
        
        replaceImageUrls(html, replacements) {
            let result = html;
            replacements.forEach(replacement => {
                result = result.replace(
                    new RegExp(escapeRegExp(replacement.originalUrl), 'g'),
                    replacement.newUrl
                );
            });
            return result;
        }
    }
};

function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

后端实现方案

文档处理服务接口
// WordProcessController.java
@RestController
@RequestMapping("/api/word")
public class WordProcessController {
    
    @Autowired
    private WordProcessingService wordService;
    
    @Autowired
    private ImageStorageService storageService;
    
    @PostMapping("/process")
    public ResponseEntity processWordContent(@RequestBody WordProcessRequest request) {
        try {
            String processedHtml = wordService.processWordHtml(request.getContent());
            return ResponseEntity.ok(new ApiResponse(true, "处理成功", processedHtml));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "处理失败: " + e.getMessage(), null));
        }
    }
    
    @PostMapping("/import")
    public ResponseEntity importWordFile(@RequestParam("file") MultipartFile file) {
        try {
            String htmlContent = wordService.importWordFile(file);
            return ResponseEntity.ok(new ApiResponse(true, "导入成功", htmlContent));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "导入失败: " + e.getMessage(), null));
        }
    }
    
    @PostMapping("/import-office")
    public ResponseEntity importOfficeFile(@RequestParam("file") MultipartFile file) {
        try {
            OfficeImportResult result = wordService.importOfficeFile(file);
            return ResponseEntity.ok(new ApiResponse(true, "导入成功", result));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "导入失败: " + e.getMessage(), null));
        }
    }
}
图片上传服务
// ImageStorageController.java
@RestController
@RequestMapping("/api/image")
public class ImageStorageController {
    
    @Autowired
    private ImageStorageService storageService;
    
    @PostMapping("/upload")
    public ResponseEntity uploadImage(@RequestBody ImageUploadRequest request) {
        try {
            String imageUrl = storageService.uploadFromUrl(request.getUrl());
            return ResponseEntity.ok(new ApiResponse(true, "上传成功", imageUrl));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "上传失败: " + e.getMessage(), null));
        }
    }
    
    @PostMapping("/upload-binary")
    public ResponseEntity uploadBinary(@RequestParam("file") MultipartFile file) {
        try {
            String imageUrl = storageService.uploadBinary(file.getBytes(), file.getOriginalFilename());
            return ResponseEntity.ok(new ApiResponse(true, "上传成功", imageUrl));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "上传失败: " + e.getMessage(), null));
        }
    }
}
存储服务实现
// AliyunOssStorageService.java
@Service
public class AliyunOssStorageService implements ImageStorageService {
    
    @Value("${oss.endpoint}")
    private String endpoint;
    
    @Value("${oss.accessKeyId}")
    private String accessKeyId;
    
    @Value("${oss.accessKeySecret}")
    private String accessKeySecret;
    
    @Value("${oss.bucketName}")
    private String bucketName;
    
    @Override
    public String uploadFromUrl(String imageUrl) throws IOException {
        // 下载图片
        byte[] imageData = downloadImage(imageUrl);
        String fileName = generateFileName(imageUrl);
        return uploadToOss(imageData, fileName);
    }
    
    @Override
    public String uploadBinary(byte[] data, String originalFileName) {
        String fileName = generateFileName(originalFileName);
        return uploadToOss(data, fileName);
    }
    
    private byte[] downloadImage(String imageUrl) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(imageUrl);
        
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                return EntityUtils.toByteArray(entity);
            }
            throw new IOException("无法获取图片数据");
        }
    }
    
    private String uploadToOss(byte[] data, String fileName) {
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
        try {
            ossClient.putObject(bucketName, fileName, new ByteArrayInputStream(data));
            return generateFileUrl(fileName);
        } finally {
            ossClient.shutdown();
        }
    }
    
    private String generateFileName(String originalName) {
        String extension = originalName.substring(originalName.lastIndexOf("."));
        return UUID.randomUUID().toString() + extension;
    }
    
    private String generateFileUrl(String fileName) {
        return String.format("https://%s.%s/%s", bucketName, endpoint, fileName);
    }
}

信创环境兼容性实现

跨平台兼容层设计

为确保在各类信创环境中稳定运行,我们设计了兼容层:

  1. 浏览器兼容层
// browser-compat.js
export function isIE8() {
    return navigator.userAgent.indexOf('MSIE 8.0') > -1;
}

export function addEvent(element, eventName, handler) {
    if (isIE8()) {
        element.attachEvent('on' + eventName, handler);
    } else {
        element.addEventListener(eventName, handler, false);
    }
}

export function getComputedStyle(element, property) {
    if (isIE8()) {
        return element.currentStyle[property];
    } else {
        return window.getComputedStyle(element, null)[property];
    }
}
  1. 国产操作系统适配
// OSUtils.java
public class OSUtils {
    
    public static boolean isKylin() {
        return System.getProperty("os.name").toLowerCase().contains("kylin");
    }
    
    public static boolean isUOS() {
        return System.getProperty("os.name").toLowerCase().contains("uos");
    }
    
    public static String getFontDir() {
        if (isWindows()) {
            return "C:\\Windows\\Fonts";
        } else if (isKylin() || isUOS()) {
            return "/usr/share/fonts";
        } else {
            return "/usr/local/share/fonts";
        }
    }
    
    // 其他操作系统检测方法...
}

部署与集成方案

前端集成步骤

  1. 将插件脚本添加到UEditor插件目录
  2. 在UEditor配置中添加插件:
// ueditor.config.js
window.UEDITOR_CONFIG = {
    // ...其他配置
    wordImportDialogUrl: '/static/word-import-dialog.html',
    toolbars: [
        ['wordImport', ...], // 在工具栏中添加按钮
        // ...其他工具栏配置
    ]
};
  1. 在Vue/React项目中引入组件:
// main.js (Vue2)
import Vue from 'vue';
import WechatImport from './plugins/wechat-import';

Vue.use(WechatImport);

// 或React中使用高阶组件
import { withWechatImport } from './plugins/wechat-import';

const EnhancedComponent = withWechatImport(MyComponent);

后端部署步骤

  1. 部署文档处理服务
  2. 配置存储服务参数
  3. 添加数据库表结构:
CREATE TABLE `sys_document_import` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `original_name` varchar(255) DEFAULT NULL,
  `file_size` bigint(20) DEFAULT NULL,
  `file_type` varchar(50) DEFAULT NULL,
  `storage_path` varchar(500) DEFAULT NULL,
  `import_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `import_user` varchar(50) DEFAULT NULL,
  `status` tinyint(4) DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `sys_image_storage` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `original_url` varchar(500) DEFAULT NULL,
  `storage_url` varchar(500) DEFAULT NULL,
  `storage_type` varchar(50) DEFAULT NULL,
  `file_size` bigint(20) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `create_user` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_original_url` (`original_url`(255)),
  KEY `idx_storage_url` (`storage_url`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

商务与技术保障方案

源代码买断方案

基于贵司年项目量500+、预算98万内的要求,我们建议以下方案:

  1. 源代码买断:98万元一次性买断,包含:

    • 全套前后端源代码
    • 五年免费技术支持
    • 完整技术文档和开发手册
    • 开发团队培训服务
  2. 资质材料

    • 5个以上央企/政府项目合同
    • 转账凭证
    • 信创环境兼容认证书
    • 软件著作权证书
    • 营业执照等全套资质
  3. 知识产权

    • 提供软件著作权转让协议
    • 代码所有权完全转移

技术保障措施

  1. 性能监控:集成APM监控系统
  2. 安全审计:提供源代码安全审计报告
  3. 灾备方案:双活数据中心部署建议
  4. 国产化适配:提供全系列国产化环境测试报告

结语

本方案针对贵司需求提供了完整的Word和微信公众号内容导入解决方案,具有以下优势:

  1. 无缝集成:作为UEditor插件形式提供,不影响现有系统
  2. 全面兼容:支持所有信创环境和浏览器
  3. 自主可控:源代码买断,满足政府和国企要求
  4. 扩展性强:存储服务可灵活切换,支持未来升级
  5. 成本优化:一次采购,全集团无限制使用

我们可提供全套源代码和技术支持,确保项目顺利实施并长期稳定运行。

复制插件目录

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

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐