服務(wù)端Word文件模板書(shū)簽替換、轉(zhuǎn)換文件類型的另類實(shí)現(xiàn)
市面上處理文字的的辦公軟件有很多,包括WPS、MSOffice、永中OFFICE,當(dāng)然還有開(kāi)源的openoffice、liboffice等。我們?cè)陧?xiàng)目開(kāi)發(fā)過(guò)程中經(jīng)常會(huì)遇到預(yù)覽word文件,數(shù)據(jù)庫(kù)中數(shù)據(jù)自動(dòng)填充word模板等需求?,F(xiàn)在能夠滿足以上需求的技術(shù)有很多,服務(wù)端可通過(guò)POI\aspose等處理,也可通過(guò)客戶端調(diào)用OFFICE組件處理,本人曾經(jīng)在這方便做了很多測(cè)試,最終發(fā)現(xiàn)兼容性最好的、接口對(duì)JAVA程序員最友好的就屬永中OFFICE,因?yàn)樗揪褪荍AVA實(shí)現(xiàn)的,使用起來(lái)非常方便。
我的測(cè)試環(huán)境使用的是永中2016版本,它運(yùn)行要求JRE1.6,且我發(fā)現(xiàn)它應(yīng)該是對(duì)JRE進(jìn)行過(guò)重構(gòu),按永中SDK要求編寫(xiě)代碼通過(guò)自O(shè)RACAL官網(wǎng)下載的jdk1.6編譯后運(yùn)行是失敗的,現(xiàn)在都2021年了,我們的項(xiàng)目絕大多數(shù)都JDK1.8以上版本了,那么怎么讓SDK兼容我們的項(xiàng)目呢?怎么實(shí)現(xiàn)標(biāo)題中提到的兩個(gè)需求呢?下面我說(shuō)說(shuō)我的處理方法吧:
1、下載永中軟件并安裝(官網(wǎng)下載即可)
2、安裝后打開(kāi)安裝路徑可以看到如下圖
JRE:即永中軟件的運(yùn)行環(huán)境
Yozo_Office.jar: 即永中為開(kāi)發(fā)者提供的SDK,可以將jar導(dǎo)入到工程中
3、編寫(xiě)WORD文件處理服務(wù)組件
處理word文件的代碼片段,詳細(xì)代碼請(qǐng)?jiān)谖暮笙螺d源碼查閱
- 處理word文件的代碼片段,詳細(xì)代碼請(qǐng)?jiān)谖暮笙螺d源碼查閱
- /**
- * 將word文件轉(zhuǎn)換為對(duì)應(yīng)格式的文件的字節(jié)數(shù)組
- * @param type 將word文件轉(zhuǎn)換成的文件格式 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())) {//此功能轉(zhuǎn)換后亂碼,后期可采用 this.workbook.saveAs("D:/2.html"); 方式存儲(chǔ)html后,將字節(jié)返回
- typePdf= FileConstants.FILETYPE_HTML;
- }else if("ofd".equals(type.toLowerCase())) {
- typePdf= FileConstants.TYPE_OFD; // 這個(gè)是不成功的,應(yīng)該是版本太低
- }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模板中的書(shū)簽
- * @param jsonObject 數(shù)據(jù)內(nèi)容 {“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);
- }
- }
- }
- /**
- * 導(dǎo)出數(shù)據(jù)成excel文件
- * @param jsonObject 數(shù)據(jù)內(nèi)容 {“bookmarkname”:”test“}
- */
- public byte[] exportData2File(JSONArray taskArray,int allrow) {
- }
4、(重點(diǎn))解決word文件處理組件與我們的項(xiàng)目文件交互問(wèn)題
本人通過(guò)SOCKET即時(shí)通訊服務(wù)解決數(shù)據(jù)交互問(wèn)題
- /**
- * 文件傳輸Server端<br>
- * 功能說(shuō)明:
- * @Author 空中智囊
- * @Date 2016年09月01日
- * @version 1.0
- */
- public class SocketService extends ServerSocket {
- private static final int SERVER_PORT = 8899; // 服務(wù)端端口
- private WordUtil wordUtil=null;
- public SocketService() throws Exception {
- super(SERVER_PORT);
- this.wordUtil=new WordUtil();
- }
- /**
- * 使用線程處理每個(gè)客戶端傳輸?shù)奈募?nbsp;
- * @throws Exception
- */
- public void load() throws Exception {
- System.out.println("服務(wù)端啟動(dòng),監(jiān)聽(tīng)端口為:"+SERVER_PORT);
- while (true) {
- // server嘗試接收其他Socket的連接請(qǐng)求,server的accept方法是阻塞式的
- Socket socket = this.accept();
- socket.setSoTimeout(1200000);
- /**
- * 我們的服務(wù)端處理客戶端的連接請(qǐng)求是同步進(jìn)行的, 每次接收到來(lái)自客戶端的連接請(qǐng)求后,
- * 都要先跟當(dāng)前的客戶端通信完之后才能再處理下一個(gè)連接請(qǐng)求。 這在并發(fā)比較多的情況下會(huì)嚴(yán)重影響程序的性能,
- * 為此,我們可以把它改為如下這種異步處理與客戶端通信的方式
- */
- // 每接收到一個(gè)Socket就建立一個(gè)新的線程來(lái)處理它
- new Thread(new Task(socket,wordUtil)).start();
- }
- }
- /**
- * 入口
- * @param args
- */
- public static void main(String[] args) {
- try {
- SocketService server = new SocketService(); // 啟動(dòng)服務(wù)端
- server.load();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 處理客戶端傳輸過(guò)來(lái)的文件線程類
- */
- 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");
- /**
- * 轉(zhuǎn)換要求的格式
- */
- try {
- /********************************讀取文件信息********************************/
- dis = new DataInputStream(socket.getInputStream());
- // 文件名和長(zhǎng)度
- String fileName = dis.readUTF();//1、文件名字
- long fileLength = dis.readLong();//2、長(zhǎng)度
- String toext = dis.readUTF();//3、擴(kuò)展名
- String taskType=dis.readUTF();//4、文件操作類型
- System.out.println("針對(duì)文件的操作類型====="+taskType);
- String valueObject=dis.readUTF();//5、替換書(shū)簽的值
- System.out.println(format.format(new Date())+":開(kāi)始接收文件");
- 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+",實(shí)際接收文件大小="+filebytes.length);
- /********************************讀取文件信息結(jié)束********************************/
- dos = new DataOutputStream(socket.getOutputStream());
- /********************************校驗(yàn)文件信息********************************/
- boolean process=true;
- if(fileLength>0){
- }else{
- dos.writeUTF("error");
- dos.flush();
- dos.writeUTF("文件沒(méi)有任何內(nèi)容,請(qǐng)重新傳送");
- dos.flush();
- process=false;
- }
- if(filebytes.length!=fileLength){
- dos.writeUTF("error");
- dos.flush();
- dos.writeUTF("接受文件與實(shí)際文件大小不符合,請(qǐng)重新傳送文件");
- dos.flush();
- process=false;
- }
- /********************************校驗(yàn)文件信息結(jié)束********************************/
- /********************************處理文件********************************/
- if(process){
- byte[] fileBytes=null;
- this.wordUtil.openFile(filebytes,fileName);//打開(kāi)院文件
- //workbook =workbooks.createWorkbookFromByteArray(filebytes,fileName);
- String lowerExt = toext.toLowerCase();
- if("convertFile".equals(taskType)){
- System.out.println("開(kāi)始將文件["+fileName+"]轉(zhuǎn)換成===="+lowerExt);
- fileBytes=this.wordUtil.convertFile(lowerExt);
- System.out.println(format.format(new Date())+":轉(zhuǎn)換"+toext+"完成");
- }else if("replaceBookMark".equals(taskType)){
- System.out.println("開(kāi)始將文件["+fileName+"]書(shū)簽進(jìn)行替換====");
- JSONObject jsonObject = JSONObject.fromObject(valueObject);
- this.wordUtil.replaceBookMark(jsonObject);
- fileBytes = this.wordUtil.convertFile(lowerExt);
- System.out.println("===============替換書(shū)簽完成============");
- }else if("exportTask".equals(taskType)) {//處理業(yè)務(wù)數(shù)據(jù) 導(dǎo)出任務(wù)數(shù)據(jù)
- System.out.println("開(kāi)始導(dǎo)出業(yè)務(wù)數(shù)據(jù)===="+valueObject);
- ServiceUtil serviceUtil = new ServiceUtil(this.wordUtil);
- JSONObject jsonObject = JSONObject.fromObject(valueObject);
- fileBytes = serviceUtil.exportData2File(jsonObject.getJSONArray("datalist"), jsonObject.getInt("size"));
- System.out.println("===============導(dǎo)出業(yè)務(wù)數(shù)據(jù)完成============");
- }
- /********************************處理文件結(jié)束********************************/
- if(fileBytes==null){
- dos.writeUTF("error");
- dos.flush();
- dos.writeUTF("處理文件過(guò)程中錯(cuò)誤");
- dos.flush();
- process=false;
- }
- /********************************返回處理過(guò)的文件********************************/
- if(process){
- dos.writeUTF("info");//文件處理完成,將信息返回到客戶端
- dos.flush();
- int fileBytelength = fileBytes.length;//轉(zhuǎn)換后的文件長(zhǎng)度
- System.out.println(format.format(new Date())+":======== 服務(wù)端開(kāi)始發(fā)送文件流,文件大?。?quot;+getFormatFileSize(fileBytelength)+") ========");
- dos.writeLong(fileBytelength);
- dos.flush();
- dos.write(fileBytes, 0, fileBytelength);//將文件一起寫(xiě)入到輸出流發(fā)送
- dos.flush();
- System.out.println(format.format(new Date())+":======== 發(fā)送文件流成功 ========");
- }
- /********************************返回處理過(guò)的文件完成********************************/
- }
- } 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("處理文件過(guò)程中錯(cuò)誤=="+e.toString());
- dos.flush();
- }catch (Exception ex){
- String exrror =ex.toString();
- System.out.println("返回?cái)?shù)據(jù)處理錯(cuò)誤信息==================="+exrror);
- }
- }finally {
- System.out.println("關(guān)閉資源");
- 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>
- * 功能說(shuō)明:
- * @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文件組件處理服務(wù)IP地址
- private static final int SERVER_PORT = 8899; // word文件組件處理服務(wù)端口
- private int soTimeout = 60000; // 服務(wù)鏈接超時(shí)時(shí)間 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() + "] 成功連接服務(wù)端");
- }
- public SocketClient() throws Exception {
- super(SERVER_IP, SERVER_PORT);
- this.setSoTimeout(this.soTimeout);
- LOGGER.info("Cliect[port:" + this.client.getLocalPort() + "] 成功連接服務(wù)端");
- }
- public SocketClient(String listenip, int listenport, int soTimeout) throws Exception {
- super(listenip, listenport);
- this.setSoTimeout(soTimeout);
- LOGGER.info("Cliect[port:" + this.client.getLocalPort() + "] 成功連接服務(wù)端");
- }
- /**
- * 處理word文件
- * @param srcRealPath 模板word文件路徑絕對(duì)地址
- * @param descRealPath 處理后的文件存放地址絕對(duì)路徑
- * @param taskType 處理文件的類型 convertFile/replaceBookMark/exportTask
- * @param jsonObject 傳給服務(wù)端的數(shù)據(jù)對(duì)象,這個(gè)參數(shù)可根據(jù)服務(wù)端需求進(jìn)行調(diào)整
- * @return 處理結(jié)果
- */
- 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());//文件長(zhǎng)度
- this.dos.flush();
- String ext = descRealPath.substring(descRealPath.lastIndexOf(".") + 1, descRealPath.length());
- this.dos.writeUTF(ext);//源文件后綴名字
- this.dos.flush();
- this.dos.writeUTF(taskType);//任務(wù)類型
- this.dos.flush();
- if (YOZOOfficeUtil.PROCESS_TYPE_CONVERTFILE.equals(taskType)) {
- this.dos.writeUTF(jsonObject.toString());
- this.dos.flush();
- }
- LOGGER.info("======== 開(kāi)始向服務(wù)端傳送源文件" + 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("======== 開(kāi)始轉(zhuǎn)換" + 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("======== 轉(zhuǎn)換" + ext + "完成,文件大?。?quot; + l / 1048576L + ")M ========");
- LOGGER.info("======== 開(kāi)始接受" + 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 = "鏈接被強(qiáng)制關(guān)閉....";
- }
- } else {
- code = "404";
- message = "文件不存在,或已被占用:" + srcRealPath;
- }
- } catch (Exception e) {
- code = "500";
- message = "客戶端報(bào)錯(cuò):" + 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();
- // 將文檔轉(zhuǎn)換成pdf文件
- socketClient.processOffice("D:/2.doc","D:/2.pdf",YOZOOfficeUtil.PROCESS_TYPE_CONVERTFILE,null);
- // 將文檔轉(zhuǎn)換成pdf文件
- JSONObject dataObject = new JSONObject();
- dataObject.element("bookmarkname","這個(gè)是測(cè)試呢日哦那個(gè)");
- socketClient.processOffice("D:/2.doc","D:/2.pdf",YOZOOfficeUtil.PROCESS_TYPE_REPLACEBOOKMARK,dataObject);
- } catch (Exception e) {
- LOGGER.error("異常:",e);
- }
- }
- }
5、啟動(dòng)word文件處理組件服務(wù)端

組件啟動(dòng)腳本
nohup ./ofdServer.sh &
6、調(diào)用服務(wù)端對(duì)word文件處理
- public static void main(String[] args) {
- try {
- SocketClient socketClient = new SocketClient();
- // 將文檔轉(zhuǎn)換成pdf文件
- socketClient.processOffice("D:/2.doc","D:/2.pdf",YOZOOfficeUtil.PROCESS_TYPE_CONVERTFILE,null);
- // 替換模板中的書(shū)簽值,word中插入書(shū)簽自行百度
- JSONObject dataObject = new JSONObject();
- dataObject.element("bookmarkname","這個(gè)是測(cè)試呢日哦那個(gè)");
- socketClient.processOffice("D:/2.doc","D:/3.doc",YOZOOfficeUtil.PROCESS_TYPE_REPLACEBOOKMARK,dataObject);
- } catch (Exception e) {
- LOGGER.error("異常:",e);
- }
- }
7、資源下載
word文件處理組件服務(wù)端(開(kāi)箱即用):
鏈接:https://pan.baidu.com/s/1_ZgjoX_nuv3a7_SKkJ_D7w 提取碼: hn2r
服務(wù)端資源內(nèi)容
將文件復(fù)制到linux服務(wù)器,并解壓,執(zhí)行 ./ofdServer.sh ,輸出:服務(wù)端啟動(dòng),監(jiān)聽(tīng)端口為:8899,即運(yùn)行成功
word文件處理組件客戶端(開(kāi)箱即用processOffice):
鏈接:https://pan.baidu.com/s/1mtabGY87RuAGGkwKrBIvfQ 提取碼: mqxf
將源文件復(fù)制到項(xiàng)目指定包名,運(yùn)行SocketClient.java中的main方法,可查看運(yùn)行結(jié)果。
最重要的一點(diǎn):服務(wù)器要安裝永中OFFICE客戶端