市面上处理文字的的办公软件有很多,包括WPS、MSOffice、永中OFFICE,当然还有开源的openoffice、liboffice等。我们在项目开发过程中经常会遇到预览word文件,数据库中数据自动填充word模板等需求。现在能够满足以上需求的技术有很多,服务端可通过POI\aspose等处理,也可通过客户端调用OFFICE组件处理,本人曾经在这方便做了很多测试,最终发现兼容性最好的、接口对JAVA程序员最友好的就属永中OFFICE,因为它基本就是JAVA实现的,使用起来非常方便。
我的测试环境使用的是永中2016版本,它运行要求JRE1.6,且我发现它应该是对JRE进行过重构,按永中SDK要求编写代码通过自ORACAL官网下载的jdk1.6编译后运行是失败的,现在都2021年了,我们的项目绝大多数都JDK1.8以上版本了,那么怎么让SDK兼容我们的项目呢?怎么实现标题中提到的两个需求呢?下面我说说我的处理方法吧:
1、下载永中软件并安装(官网下载即可)
2、安装后打开安装路径可以看到如下图
JRE:即永中软件的运行环境
Yozo_Office.jar: 即永中为开发者提供的SDK,可以将jar导入到工程中
3、编写WORD文件处理服务组件
处理word文件的代码片段,详细代码请在文后下载源码查阅
- 处理word文件的代码片段,详细代码请在文后下载源码查阅
- /**
- * 将word文件转换为对应格式的文件的字节数组
- * @param type 将word文件转换成的文件格式 pdf、html\ofd\txt\xml
- * @return
- * @throws IOException
- */
- public byte[] convertFile(String type) throws IOException {
- int typePdf = FileConstants.TYPE_PDF;
- if("html".equals(type.toLowerCase())) {//此功能转换后乱码,后期可采用 this.workbook.saveAs("D:/2.html"); 方式存储html后,将字节返回
- typePdf= FileConstants.FILETYPE_HTML;
- }else if("ofd".equals(type.toLowerCase())) {
- typePdf= FileConstants.TYPE_OFD; // 这个是不成功的,应该是版本太低
- }else if("txt".equals(type.toLowerCase())) {
- typePdf = FileConstants.TYPE_TXT;
- }else if("xml".equals(type.toLowerCase())) {
- typePdf = FileConstants.FILETYPE_XML;
- }else if("doc".equals(type.toLowerCase())||"xls".equals(type.toLowerCase())||"ppt".equals(type.toLowerCase())) {
- typePdf = FileConstants.TYPE_MS;
- }else if("docx".equals(type.toLowerCase())||"xlsx".equals(type.toLowerCase())||"pptx".equals(type.toLowerCase())) {
- typePdf = FileConstants.TYPE_MS_EX;
- }
- return this.workbooks.getWorkbookAsByteArray(workbook, typePdf);
- }
- /**
- * 替换word模板中的书签
- * @param jsonObject 数据内容 {“bookmarkname”:”test“}
- */
- public void replaceBookMark(JSONObject jsonObject) {
- BookMarks bookMarks = this.document.getBookMarks();
- BookMark[] allBookmarks = bookMarks.getAllBookmarks();
- for(BookMark bookMark:allBookmarks){
- String name = bookMark.getName();
- TextRange range = bookMark.getRange();
- //if(name!=null)name=name.replace("PO_","");
- String value = "";
- Object o = jsonObject.get(name);
- if(o!=null){
- value=jsonObject.get(name).toString();
- }
- try {
- range.insertText(value);
- }catch (Exception e){
- range.insertText(value);
- }
- }
- }
- /**
- * 导出数据成excel文件
- * @param jsonObject 数据内容 {“bookmarkname”:”test“}
- */
- public byte[] exportData2File(JSONArray taskArray,int allrow) {
- }
4、(重点)解决word文件处理组件与我们的项目文件交互问题
本人通过SOCKET即时通讯服务解决数据交互问题
- /**
- * 文件传输Server端<br>
- * 功能说明:
- * @Author 空中智囊
- * @Date 2016年09月01日
- * @version 1.0
- */
- public class SocketService extends ServerSocket {
- private static final int SERVER_PORT = 8899; // 服务端端口
- private WordUtil wordUtil=null;
- public SocketService() throws Exception {
- super(SERVER_PORT);
- this.wordUtil=new WordUtil();
- }
- /**
- * 使用线程处理每个客户端传输的文件
- * @throws Exception
- */
- public void load() throws Exception {
- System.out.println("服务端启动,监听端口为:"+SERVER_PORT);
- while (true) {
- // server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
- Socket socket = this.accept();
- socket.setSoTimeout(1200000);
- /**
- * 我们的服务端处理客户端的连接请求是同步进行的, 每次接收到来自客户端的连接请求后,
- * 都要先跟当前的客户端通信完之后才能再处理下一个连接请求。 这在并发比较多的情况下会严重影响程序的性能,
- * 为此,我们可以把它改为如下这种异步处理与客户端通信的方式
- */
- // 每接收到一个Socket就建立一个新的线程来处理它
- new Thread(new Task(socket,wordUtil)).start();
- }
- }
- /**
- * 入口
- * @param args
- */
- public static void main(String[] args) {
- try {
- SocketService server = new SocketService(); // 启动服务端
- server.load();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 处理客户端传输过来的文件线程类
- */
- public class Task implements Runnable {
- @Override
- public void run() {
- System.out.println("===客户端连接成功=====");
- System.out.println("****************************************************************");
- SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- /**
- * 转换要求的格式
- */
- try {
- /********************************读取文件信息********************************/
- dis = new DataInputStream(socket.getInputStream());
- // 文件名和长度
- String fileName = dis.readUTF();//1、文件名字
- long fileLength = dis.readLong();//2、长度
- String toext = dis.readUTF();//3、扩展名
- String taskType=dis.readUTF();//4、文件操作类型
- System.out.println("针对文件的操作类型====="+taskType);
- String valueObject=dis.readUTF();//5、替换书签的值
- System.out.println(format.format(new Date())+":开始接收文件");
- ByteArrayOutputStream bos = new ByteArrayOutputStream((int)fileLength);
- byte[] bytes = new byte[1024];
- int length = 0;
- while((length = dis.read(bytes, 0, bytes.length)) != -1) {
- bos.write(bytes, 0, length);
- }
- byte[] filebytes = bos.toByteArray();
- System.out.println("原始文件大小====="+fileLength+",实际接收文件大小="+filebytes.length);
- /********************************读取文件信息结束********************************/
- dos = new DataOutputStream(socket.getOutputStream());
- /********************************校验文件信息********************************/
- boolean process=true;
- if(fileLength>0){
- }else{
- dos.writeUTF("error");
- dos.flush();
- dos.writeUTF("文件没有任何内容,请重新传送");
- dos.flush();
- process=false;
- }
- if(filebytes.length!=fileLength){
- dos.writeUTF("error");
- dos.flush();
- dos.writeUTF("接受文件与实际文件大小不符合,请重新传送文件");
- dos.flush();
- process=false;
- }
- /********************************校验文件信息结束********************************/
- /********************************处理文件********************************/
- if(process){
- byte[] fileBytes=null;
- this.wordUtil.openFile(filebytes,fileName);//打开院文件
- //workbook =workbooks.createWorkbookFromByteArray(filebytes,fileName);
- String lowerExt = toext.toLowerCase();
- if("convertFile".equals(taskType)){
- System.out.println("开始将文件["+fileName+"]转换成===="+lowerExt);
- fileBytes=this.wordUtil.convertFile(lowerExt);
- System.out.println(format.format(new Date())+":转换"+toext+"完成");
- }else if("replaceBookMark".equals(taskType)){
- System.out.println("开始将文件["+fileName+"]书签进行替换====");
- JSONObject jsonObject = JSONObject.fromObject(valueObject);
- this.wordUtil.replaceBookMark(jsonObject);
- fileBytes = this.wordUtil.convertFile(lowerExt);
- System.out.println("===============替换书签完成============");
- }else if("exportTask".equals(taskType)) {//处理业务数据 导出任务数据
- System.out.println("开始导出业务数据===="+valueObject);
- ServiceUtil serviceUtil = new ServiceUtil(this.wordUtil);
- JSONObject jsonObject = JSONObject.fromObject(valueObject);
- fileBytes = serviceUtil.exportData2File(jsonObject.getJSONArray("datalist"), jsonObject.getInt("size"));
- System.out.println("===============导出业务数据完成============");
- }
- /********************************处理文件结束********************************/
- if(fileBytes==null){
- dos.writeUTF("error");
- dos.flush();
- dos.writeUTF("处理文件过程中错误");
- dos.flush();
- process=false;
- }
- /********************************返回处理过的文件********************************/
- if(process){
- dos.writeUTF("info");//文件处理完成,将信息返回到客户端
- dos.flush();
- int fileBytelength = fileBytes.length;//转换后的文件长度
- System.out.println(format.format(new Date())+":======== 服务端开始发送文件流,文件大小("+getFormatFileSize(fileBytelength)+") ========");
- dos.writeLong(fileBytelength);
- dos.flush();
- dos.write(fileBytes, 0, fileBytelength);//将文件一起写入到输出流发送
- dos.flush();
- System.out.println(format.format(new Date())+":======== 发送文件流成功 ========");
- }
- /********************************返回处理过的文件完成********************************/
- }
- } catch (Exception e) {
- String error = e.toString();
- System.out.println("error==================="+error);
- StackTraceElement[] stackTrace = e.getStackTrace();
- for(StackTraceElement s:stackTrace){
- int lineNumber = s.getLineNumber();
- String methodName = s.getMethodName();
- String className = s.getClassName();
- String filename = s.getFileName();
- System.out.print("err:"+filename+" "+className+" "+methodName+" "+lineNumber);
- System.out.println("");
- }
- try {
- dos.writeUTF("error");
- dos.flush();
- dos.writeUTF("处理文件过程中错误=="+e.toString());
- dos.flush();
- }catch (Exception ex){
- String exrror =ex.toString();
- System.out.println("返回数据处理错误信息==================="+exrror);
- }
- }finally {
- System.out.println("关闭资源");
- try {
- if(wordUtil!=null)wordUtil.close();
- socket.close();
- } catch (Exception e) {
- String error = e.toString();
- System.out.println(error);
- e.printStackTrace();
- }
- System.out.println("****************************************************************");
- }
- }
- /**
- * 文件传输Clinet端<br>
- * 功能说明:
- * @Author 空中智囊
- * @Date 2016年09月01日
- * @version 1.0
- */
- public class SocketClient extends Socket {
- public static final Logger LOGGER = LoggerFactory.getLogger(SocketClient.class);
- private static final String SERVER_IP = "127.0.0.1"; // word文件组件处理服务IP地址
- private static final int SERVER_PORT = 8899; // word文件组件处理服务端口
- private int soTimeout = 60000; // 服务链接超时时间 60s
- private Socket client = this;
- private FileInputStream fis;
- private DataOutputStream dos;
- private DataInputStream dis;
- private FileOutputStream fos;
- public SocketClient(String listenip, int listenport) throws Exception {
- super(listenip, listenport);
- this.setSoTimeout(this.soTimeout);
- LOGGER.info("Cliect[port:" + this.client.getLocalPort() + "] 成功连接服务端");
- }
- public SocketClient() throws Exception {
- super(SERVER_IP, SERVER_PORT);
- this.setSoTimeout(this.soTimeout);
- LOGGER.info("Cliect[port:" + this.client.getLocalPort() + "] 成功连接服务端");
- }
- public SocketClient(String listenip, int listenport, int soTimeout) throws Exception {
- super(listenip, listenport);
- this.setSoTimeout(soTimeout);
- LOGGER.info("Cliect[port:" + this.client.getLocalPort() + "] 成功连接服务端");
- }
- /**
- * 处理word文件
- * @param srcRealPath 模板word文件路径绝对地址
- * @param descRealPath 处理后的文件存放地址绝对路径
- * @param taskType 处理文件的类型 convertFile/replaceBookMark/exportTask
- * @param jsonObject 传给服务端的数据对象,这个参数可根据服务端需求进行调整
- * @return 处理结果
- */
- public JSONObject processOffice(String srcRealPath, String descRealPath, String taskType, JSONObject jsonObject) {
- JSONObject rtnObject = new JSONObject();
- String code = "200";
- String message = "";
- try {
- File file = new File(srcRealPath);
- if (!file.exists() || !file.canWrite()) {
- code = "200";
- message = "文件不存在,或已被占用";
- rtnObject.element("code", code);
- rtnObject.element("message", message);
- JSONObject var41 = rtnObject;
- return var41;
- }
- LOGGER.info(srcRealPath + "===>" + descRealPath);
- if (file.exists() && file.canWrite()) {
- String filename = file.getName();
- this.fis = new FileInputStream(file);
- this.dos = new DataOutputStream(this.client.getOutputStream());
- this.dos.writeUTF(filename);//文件名字
- this.dos.flush();
- this.dos.writeLong(file.length());//文件长度
- this.dos.flush();
- String ext = descRealPath.substring(descRealPath.lastIndexOf(".") + 1, descRealPath.length());
- this.dos.writeUTF(ext);//源文件后缀名字
- this.dos.flush();
- this.dos.writeUTF(taskType);//任务类型
- this.dos.flush();
- if (YOZOOfficeUtil.PROCESS_TYPE_CONVERTFILE.equals(taskType)) {
- this.dos.writeUTF(jsonObject.toString());
- this.dos.flush();
- }
- LOGGER.info("======== 开始向服务端传送源文件" + srcRealPath + " ========");
- byte[] bytes = new byte[1024];
- long progress = 0L;
- int length;
- while((length = this.fis.read(bytes, 0, bytes.length)) != -1) {
- this.dos.write(bytes, 0, length);
- this.dos.flush();
- progress += (long)length;
- LOGGER.info("| " + 100L * progress / file.length() + "% |");
- }
- LOGGER.info("======== 文件传输成功 (" + file.length() / 1048576L + ")M========");
- this.client.shutdownOutput();
- LOGGER.info("======== 开始转换" + ext + " ========");
- InputStream inputStream = this.client.getInputStream();
- this.dis = new DataInputStream(inputStream);
- String result = this.dis.readUTF();
- if ("error".equals(result)) {
- String reason = this.dis.readUTF();
- LOGGER.info(reason);
- code = "500";
- message = reason;
- } else if ("info".equals(result)) {
- long l = this.dis.readLong();
- LOGGER.info("======== 转换" + ext + "完成,文件大小(" + l / 1048576L + ")M ========");
- LOGGER.info("======== 开始接受" + ext + " ========");
- File newFile = new File(descRealPath);
- if (newFile.exists()) {
- newFile.delete();
- }
- this.fos = new FileOutputStream(newFile);
- progress = 0L;
- bytes = new byte[1048576];
- while((length = this.dis.read(bytes, 0, bytes.length)) != -1) {
- this.fos.write(bytes, 0, length);
- this.fos.flush();
- }
- LOGGER.info("======== 接受" + ext + "文件成功========");
- this.dis.close();
- } else {
- code = "500";
- message = "链接被强制关闭....";
- }
- } else {
- code = "404";
- message = "文件不存在,或已被占用:" + srcRealPath;
- }
- } catch (Exception e) {
- code = "500";
- message = "客户端报错:" + e.toString();
- LOGGER.error("异常:",e);
- } finally {
- if (this.fis != null) {
- try {
- this.fis.close();
- } catch (Exception var38) {
- ;
- }
- }
- if (this.fos != null) {
- try {
- this.fos.close();
- } catch (Exception var37) {
- ;
- }
- }
- try {
- this.client.close();
- } catch (Exception var36) {
- ;
- }
- }
- rtnObject.element("code", code);
- rtnObject.element("message", message);
- return rtnObject;
- }
- public static void main(String[] args) {
- try {
- SocketClient socketClient = new SocketClient();
- // 将文档转换成pdf文件
- socketClient.processOffice("D:/2.doc","D:/2.pdf",YOZOOfficeUtil.PROCESS_TYPE_CONVERTFILE,null);
- // 将文档转换成pdf文件
- JSONObject dataObject = new JSONObject();
- dataObject.element("bookmarkname","这个是测试呢日哦那个");
- socketClient.processOffice("D:/2.doc","D:/2.pdf",YOZOOfficeUtil.PROCESS_TYPE_REPLACEBOOKMARK,dataObject);
- } catch (Exception e) {
- LOGGER.error("异常:",e);
- }
- }
- }
5、启动word文件处理组件服务端
组件启动脚本
nohup ./ofdServer.sh &
6、调用服务端对word文件处理
- public static void main(String[] args) {
- try {
- SocketClient socketClient = new SocketClient();
- // 将文档转换成pdf文件
- socketClient.processOffice("D:/2.doc","D:/2.pdf",YOZOOfficeUtil.PROCESS_TYPE_CONVERTFILE,null);
- // 替换模板中的书签值,word中插入书签自行百度
- JSONObject dataObject = new JSONObject();
- dataObject.element("bookmarkname","这个是测试呢日哦那个");
- socketClient.processOffice("D:/2.doc","D:/3.doc",YOZOOfficeUtil.PROCESS_TYPE_REPLACEBOOKMARK,dataObject);
- } catch (Exception e) {
- LOGGER.error("异常:",e);
- }
- }
7、资源下载
word文件处理组件服务端(开箱即用):
链接:https://pan.baidu.com/s/1_ZgjoX_nuv3a7_SKkJ_D7w 提取码: hn2r
服务端资源内容
将文件复制到linux服务器,并解压,执行 ./ofdServer.sh ,输出:服务端启动,监听端口为:8899,即运行成功
word文件处理组件客户端(开箱即用processOffice):
链接:https://pan.baidu.com/s/1mtabGY87RuAGGkwKrBIvfQ 提取码: mqxf
将源文件复制到项目指定包名,运行SocketClient.java中的main方法,可查看运行结果。
最重要的一点:服务器要安装永中OFFICE客户端