Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
章最后提供源碼下載地址
市面上處理文字的的辦公軟件有很多,包括WPS、MSOffice、永中OFFICE,當然還有開源的openoffice、liboffice等。我們在項目開發過程中經常會遇到預覽word文件,數據庫中數據自動填充word模板等需求?,F在能夠滿足以上需求的技術有很多,服務端可通過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文件轉換為對應格式的文件的字節數組
* @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 + "完成,文件大?。?#34; + 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客戶端
數據報表是許多項目都有的模塊,一般都是導出Excel或者PDF,這里記錄下我在項目里用POI導出Excel。項目中,我需要根據頁面jqgrid的機架查詢條件導出對應的機架數據,jqgrid是分頁的,但導出是要導出所有。
Apache POI - the Java API for Microsoft Documents,官網:http://poi.apache.org/
maven引入POI
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
或者
<dependency><!--Excel工具類(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.2.0</version>
</dependency>
<dependency><!--Excel工具類(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency><!--Excel工具類(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>3.2.0</version>
</dependency>
html、js調用
<dependency><!--Excel工具類(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.2.0</version>
</dependency>
<dependency><!--Excel工具類(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency><!--Excel工具類(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>3.2.0</version>
</dependency>
//導出excel
function exportRackExcel() {
//獲取當前jqGrid分頁參數
var postData = $("#rack").jqGrid("getGridParam", "postData");
postData.page = 1;
postData.rows = 999999999;//設置每頁9億條記錄(相當于無窮大,查詢所有)
//ajax不支持Excel類型,使用location.href或者表單提交
//window.location.href,get提交,數據會暴露在URL,相對不安全
//創建臨時的、隱藏的form表單,post提交,數據在請求體里,相對安全
var $form = $(document.createElement('form')).css({display: 'none'}).attr("method", "POST").attr("action", ctx + "/excel");
for (var key in postData) {
var $input = $(document.createElement('input')).attr('name', key).val(postData[key]);
$form.append($input);
}
$("body").append($form);
$form.submit();
//過河拆橋,提交完成后remove掉
$form.remove();
}
純js寫法
//其他操作,同上
let $form = document.createElement('form');
$form.style.display="none";
$form.method="POST";
$form.action=ctx + "/excel";
for (let key in postData) {
if(postData[key]){
let $input = document.createElement('input');
$input.name=key;
$input.value=postData[key];
$form.appendChild($input);
}
}
document.body.appendChild($form);
$form.submit();
//過河拆橋,提交完成后remove掉
$form.remove();
controller
/**
* 根據當前jqGrid分頁情況,創建并導出Excel文件
*
* @param entity 機架實體,用來接收查詢條件
* @return ResponseEntity
*/
@PostMapping("/excel")
public ResponseEntity createExcel(RackVo entity) {
//Excel對應的columnNames列名集合 { key,label }
String[][] excelMap = {
{"no", "Rack Code"},
{"rackName", "Rack Name"},
{"roomName", "Room"},
{"idc", "IDC Center"},
{"clientName", "Customer"},
{"rackTypeName", "Type"},
{"existentialMode", "Existential Mode"},
{"maxPower", "Maximum Power(KVA)"},
{"status", "Status"},
{"administrate", "Administrate"},
};
return DownloadUtil.download(ExportExcelUtil.createExcel("Rack Management", excelMap, rackService.createExcel(entity).getData()).getData(), "機架數據報表");
}
兩個工具類:導出Excel工具類 ExportExcelUtil,下載工具類 DownloadUtil
/**
* java POI 導出Excel表工具類
*/
public class ExportExcelUtil {
//禁止實例化
private ExportExcelUtil() {
}
/**
* 只支持一級表頭
*
* @param titleName 表標題
* @param columnNames 列名集合,key是用來設置填充數據時對應單元格的值,label就是對應的列名,生成Excel表時,
* 第一維數組下標0對應值為Excel表最左邊的列的列名 例:{ { key,label },{ key,label } }
* @param dataLists 數據集合,key對應的是列名集合的key,value是要填充到單元格的值 例:ArrayList<HashMap<String key, String vaule>>
* @return ResultModel<Workbook>
*/
public static ResultModel<Workbook> createExcel(String titleName, String[][] columnNames, ArrayList<HashMap<String, String>> dataLists) {
//創建HSSFWorkbook對象(excel的文檔對象)
HSSFWorkbook wb = new HSSFWorkbook();
//建立新的sheet對象(excel的表單)
HSSFSheet sheet = wb.createSheet(titleName);//設置表單名
//1、標題名
//創建標題行,參數為行索引(excel的行),可以是0~65535之間的任何一個
HSSFRow row1 = sheet.createRow(0);
//標題的字體
HSSFFont font1 = wb.createFont();
font1.setFontHeightInPoints((short) 12);
font1.setFontName("黑體");
//標題的樣式
HSSFCellStyle style1 = wb.createCellStyle();
style1.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中
style1.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中
// 把字體 應用到當前樣式
style1.setFont(font1);
//自動換行
style1.setWrapText(true);
//自定義填充顏色(天空藍)
style1.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style1.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
// 設置邊框
style1.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style1.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style1.setBorderRight(HSSFCellStyle.BORDER_THIN);
style1.setBorderTop(HSSFCellStyle.BORDER_THIN);
createCell(row1, 0, style1, titleName);
//合并單元格CellRangeAddress構造參數依次表示起始行,截至行,起始列, 截至列
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, columnNames.length - 1));
//2、列名
//創建列名行
//列名的字體
HSSFFont font2 = wb.createFont();
font2.setFontHeightInPoints((short) 12);
font2.setFontName("新宋體");
//列名的樣式
HSSFCellStyle style2 = wb.createCellStyle();
style2.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中
style2.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中
// 把字體 應用到當前樣式
style2.setFont(font2);
//自動換行
style2.setWrapText(true);
//自定義填充顏色(淺藍色)
style2.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style2.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
// 設置邊框
style2.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style2.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style2.setBorderRight(HSSFCellStyle.BORDER_THIN);
style2.setBorderTop(HSSFCellStyle.BORDER_THIN);
HSSFRow row2 = sheet.createRow(1);
for (int i = 0; i < columnNames.length; i++) {
//單元格寬度
sheet.setColumnWidth(i, 20 * 256);
createCell(row2, i, style2, columnNames[i][1]);//例:[[key,label],[key,label]] 取label
}
//3、填充數據
//內容的字體
HSSFFont font3 = wb.createFont();
font3.setFontHeightInPoints((short) 12);
font3.setFontName("新宋體");
//內容的樣式
HSSFCellStyle style3 = wb.createCellStyle();
style3.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中
style3.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中
// 把字體 應用到當前樣式
style3.setFont(font3);
//自動換行
style3.setWrapText(true);
//默認無填充
style3.setFillPattern(FillPatternType.NO_FILL);
style3.setFillForegroundColor(IndexedColors.RED.getIndex());
// 設置邊框
style3.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style3.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style3.setBorderRight(HSSFCellStyle.BORDER_THIN);
style3.setBorderTop(HSSFCellStyle.BORDER_THIN);
int index = 2;//標題行、列名行,所以數據行默認從第三行開始
for (HashMap<String, String> map : dataLists) {
//創建內容行
HSSFRow row3 = sheet.createRow(index);
for (int i = 0; i < columnNames.length; i++) {
String val = map.get(columnNames[i][0]);
createCell(row3, i, style3, val == null ? "" : val);//例:[[key,label],[key,label]] 取key
}
index++;
}
return ResultModel.of(wb);
}
/**
* 創建一個單元格
*
* @param row 行
* @param column 列
* @param cellStyle 單元格樣式
* @param text 值
*/
private static void createCell(Row row, int column, CellStyle cellStyle, String text) {
Cell cell = row.createCell(column); // 創建單元格
cell.setCellValue(text); // 設置值
cell.setCellStyle(cellStyle); // 設置單元格樣式
}
}
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 文件下載工具類
*/
public class DownloadUtil{
/**
* 快速下載
*/
public static ResponseEntity download(byte[] fileBytes, String fileName) {
//設置文件
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", new String(fileName.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1));
//下載文件
return new ResponseEntity<>(fileBytes, headers, HttpStatus.CREATED);
}
/**
* 快速下載
*/
public static ResponseEntity download(File file) {
return download(getByteArray(file), file.getName());
}
/**
* 快速下載
*/
public static ResponseEntity download(Workbook workbook, String fileName) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
fileName = fileName + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xls";
workbook.write(outputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
return download(outputStream.toByteArray(), fileName);
}
//獲取文件的字節數組
private static byte[] getByteArray(File file) {
if (!file.exists()) {
throw new RuntimeException("File Not Found:" + file.getPath());
}
ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
BufferedInputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
int buf_size = 1024;
byte[] buffer = new byte[buf_size];
int len;
while (-1 != (len = in.read(buffer, 0, buf_size))) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
try {
assert in != null;
in.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//獲取文件名后綴
private static String getSuffix(String fileName) {
int lastPointIndex = fileName.lastIndexOf(".");
if (StringUtils.isEmpty(fileName) || lastPointIndex == -1) {
return null;
}
return fileName.substring(lastPointIndex + 1);
}
}
獲取封裝數據的service層 createExcel,直接到取page分頁方法,遍歷機架數據集合,設置Map<key,value>,add到list<Map>中,最后將封裝好的數據return回controller,傳入工具類,最后下載。
/**
* 根據當前jqGrid分頁情況,創建并導出Excel文件
*
* @param entity 查詢條件
* @return 封裝好的數據集合
*/
@Override
public ResultModel<ArrayList<HashMap<String, String>>> createExcel(RackVo entity) {
ArrayList<HashMap<String, String>> dataLists = new ArrayList<HashMap<String, String>>();
//直接調page分頁方法,獲取當前jqGrid分頁條件對應的數據集合,
ResultModel<PageInfo<RackVo>> rm = page(entity);
if (rm.isFlag()) {
List<RackVo> rackVoList = rm.getData().getRows();
for (RackVo rackVo : rackVoList) {
HashMap<String, String> map = new HashMap<String, String>(16);
map.put("no", rackVo.getNo() != null ? rackVo.getNo() : "");
map.put("rackName", rackVo.getName() != null ? rackVo.getName() : "");
map.put("roomName", rackVo.getRoom() != null ? rackVo.getRoom().getRoomname() : "");
map.put("idc", rackVo.getOrg() != null ? rackVo.getOrg().getOrgName() : "");
map.put("clientName", rackVo.getCustomer() != null ? rackVo.getCustomer().getClientname() : "");
map.put("rackTypeName", rackVo.getRacktype() != null ? rackVo.getRacktype().getName() : "");
map.put("existentialMode", "1".equals(rackVo.getExistentialMode()) ? "Physical" : "Virtual");
map.put("maxPower", rackVo.getMaxpower() != null ? rackVo.getMaxpower() : "");
String status = rackVo.getServiceStatus();
switch (status != null ? status : "") {
case "1":
status = "Idle";
break;
case "2":
status = "Reserved";
break;
case "3":
status = "Occupied";
break;
default:
status = "";
break;
}
map.put("status", status);
String administrate = rackVo.getAdministrate();
switch (administrate != null ? administrate : "") {
case "R":
administrate = "Cust Own";
break;
case "U":
administrate = "CTG Own";
break;
default:
administrate = "";
break;
}
map.put("administrate", administrate);
dataLists.add(map);
}
}
return ResultModel.of(dataLists);
}
從開發階段到測試階段,導了無數次,沒毛病
excelMap,Excel對應的columnNames列名集合 { key,label },可以不用再controller設置了,直接從頁面jqgrid抓取,傳入controller就行(滑稽臉~)
//獲取jqgrid頭部標題tr,有多少個tr就有多少級標題
var thead_tr = $(".ui-jqgrid-htable").find("tr.ui-jqgrid-labels");
//遍歷thead_tr找出每一個標題,并保存到對象中
var titles = [];
thead_tr.each(function(index_tr,element_tr){
titles.push([]);
$(element_tr).find("th").each(function(index_th,element_th){
//內容
var label = $(element_th).text();
//所占行 rowspan 默認1
var rowspan = $(element_th).attr("rowspan") || 1;
//所占列 colspan 默認1
var colspan = $(element_th).attr("colspan") || 1;
//鍵
var key = $(element_th).attr("id");
key = key.substring(key.lastIndexOf("_")+1,key.length);
if(label){
titles[index_tr].push({
label:label,
key:key,
rowspan:rowspan,
colspan:colspan,
});
}
});
});
//JSON.stringify(titles)
console.log(titles);
2020-10-20更新
直接構造form表單提交,我們不能設置請求頭信息,有些需求不能滿足(例如在前后端分離的項目中,需要在請求頭傳遞token令牌),當我們導出Excel功能需要設置請求頭信息時應該如何操作呢?封裝原生Ajax,利用responseType: 'blob'屬性,接收二進制數據,構建Blob對象,將二進制數據轉成文件,利用a標簽下載文件
//封裝原生Ajax
var Ajax={
get: function(options) {
let xhr = new XMLHttpRequest();
xhr.open('GET', options.url, true);
//設置請求頭
xhr.setRequestHeader("Authorization", 'Bearer ' + store.getters.token);
xhr.onload = function() {
let response = null;
// responseType="" / "text"時,響應的結果從xhr.responseText獲取
if(xhr.responseType === "" || xhr.responseType === "text"){
response = xhr.responseText;
}
//200 請求成功
if (xhr.status === 200) {
options.success.call(response);
}
//其他情況,請求失敗
if(options.error){
options.error.call(xhr.error);
}
};
xhr.send();
},
post: function (options) {
let xhr = new XMLHttpRequest();
xhr.open("POST", options.url, true);
//設置請求頭
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", 'Bearer ' + store.getters.token);
//設置響應內容類型、超時時間
options.responseType ? xhr.responseType = options.responseType : xhr.responseType = "text";
options.timeout ? xhr.timeout = options.timeout : xhr.timeout = 30000;
xhr.onload = function() {
let response = null;
// responseType="" / "text"時,響應的結果從xhr.responseText獲取
if(xhr.responseType === "" || xhr.responseType === "text"){
response = xhr.responseText;
}
//200 請求成功
if (xhr.status === 200) {
options.success.call(response);
}
// responseType = "blob"時,響應的是Blob二進制數據,直接調用下載
if(xhr.status === 201){
download(xhr,options.success)
}
//其他情況,請求失敗
if(options.error){
options.error.call(xhr.error);
}
};
xhr.send(JSON.stringify(options.data));
}
};
//Blob響應,轉成文件下載
function download(response,callback) {
//創建一個隱藏的下載a標簽
let url = window.URL.createObjectURL(new Blob([response.response]));
let link = document.createElement("a");
link.style.display = "none";
link.href = url;
//設置文件名,文件名從響應頭中獲?。≒S:可能會存在中文亂碼、文件后綴多個下劃線等問題)
let fileName = response.getAllResponseHeaders().split("\n")[4].split(":")[1].split(";")[2].split("=")[1].replace(/"/g,"");
fileName = decodeURIComponent(escape(fileName));
console.log("文件名:" + fileName);
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
//過河拆橋
link.remove();
if(callback){
callback();
}
}
使用
//獲取當前分頁參數
let postData = vue.getPageParameter();
postData.page = 1;
postData.pageSize = 999999999;//設置每頁9億條記錄(相當于無窮大,查詢所有)
console.log("開始導出...");
Ajax.post({
url:vue.excelUrl,
data:postData,
timeout: 30000,
responseType: 'blob',
success:function () {
console.log("導出完成,請您注意瀏覽器的下載管理器!");
}
});
效果
后綴多了個下劃線,很奇怪...,刪除下劃線文件能正常打開,數據、單元格背景等正常
作者:huanzi-qch
出處:https://www.cnblogs.com/huanzi-qch
若標題中有“轉載”字樣,則本文版權歸原作者所有。若無轉載字樣,本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利.
產品期望實現【公文管理】其中發文擬文一塊內容,用戶上傳正文(word),再選擇不同套紅模板,最后拼接為一個對外發文,的公文格式。
基于上次使用vue實現在線編輯功能,產品不太滿意,重新學習java如何操作word文檔,之前地址:(vue使用Tinymce富文本模擬在線word文檔)juejin.cn/post/723665…
有兩個文件test1.docx (作為紅頭模板)和test2.docx(作為正文);期望實現效果:用戶選擇紅頭模板加上自己寫的正文內容,最終生成可編輯的word文檔。
create_table.docx 合并之后最終效果“
test1.docx 紅頭模板:
test2.docx 正文(用戶撰寫上傳的內容):
如標題所示,最終采用Apache POI來實現這個功能主要原因:
主要是對于我這種初學者來說是非常友好的,太復雜玩不轉。Apache POI 的使用非常簡單,只需添加它的 jar 包到項目中,并調用相應的 API 即可實現文檔讀寫。同時,Apache POI 提供了豐富的文檔和官方網站上的文檔也很詳細。
Apache POI,分別為 Word、Excel、PowerPoint 等各種格式提供不同的類方法,我們需要操作Word文檔的功能,所以使用(Java API for Microsoft Documents)中的XWPFDocument類,實現文檔合并功能。
整理不同格式文檔操作類
注意:word文檔目前有兩種不同格式,一種是以doc結尾的,另一種以docx結尾,本次功能主要講解docx格式文檔操作,doc格式文檔調用的類和函數HWPF開頭。
兩者區別:doc是Word2007及以下版的文件擴展名,而docx是Word2007及以上版本的文件擴展名,docx版本兼容性較高,而且比doc文件所占用空間更小。
在pom.xml文件中引入maven依賴,
<!-- WordToHtml .doc .odcx poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 操作excel的庫 注意版本保持一致 poi poi-ooxml poi-scratchpad -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!--poi-ooxml和*poi-ooxml-schemas*是poi對2007及以上版本的擴充。-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
簡化整體流程,創建兩個word文件test1.docx和test2.docx。將下列file對應的文件路徑換成自己創建的文件路徑創,建單個java文件(帶main),直接運行main方法輸出creat_table.docx文件。 先上代碼,再進行講解:
package org.ssssssss.magicboot;
import org.apache.poi.xwpf.usermodel.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
public class WordDocumentTest {
public static void main(String[] args) throws Exception
{
//獲取文件轉io流
File file1 = new File("D:/IDM/test1.docx");
File file2 = new File("D:/IDM/test2.docx");
FileInputStream fis1 = new FileInputStream(file1);
FileInputStream fis2 = new FileInputStream(file2);
//最終文件輸出的路徑
FileOutputStream out = new FileOutputStream(new File("D:/IDM/create_table.docx"));
//轉為word文檔元素
XWPFDocument dcx1 = new XWPFDocument(fis1);
XWPFDocument dcx2 = new XWPFDocument(fis2);
//創建一個新的文檔
XWPFDocument document= new XWPFDocument();
//將第一個文檔元素復制新創建的文檔中;
document = dcx1;
//換行 document.createParagraph().createRun().addBreak();
//將第二個docx內容添加到新創建的文檔中
mergeParagraphs(dcx2.getParagraphs(), document);
//結束關閉io流
document.write(out);
out.close();
fis1.close();
fis2.close();
System.out.println("create_table document written success.");
}
// 合并文本段落
private static void mergeParagraphs(List<XWPFParagraph> paragraphs, XWPFDocument outDoc) {
for (XWPFParagraph para : paragraphs) {
XWPFParagraph newPara = outDoc.createParagraph();
newPara.getCTP().setPPr(para.getCTP().getPPr());
//判斷是否是文本、段落、圖片 //.getRPr() para.tables !=null ,iruns 獲取圖片
for (XWPFRun run : para.getRuns()) {
XWPFRun newRun = newPara.createRun();
newRun.getCTR().setRPr(run.getCTR().getRPr());
newRun.setText(run.getText(0));
}
}
}
實現流程主要看代碼中的注釋,包含以下幾個步驟
核心重點將第二個docx內容添加到新創建的文檔中封裝mergeParagraphs方法,在這個方法中傳入了兩個參數(List paragraphs, XWPFDocument outDoc) 其中List<XWPFParagraph> paragraphs = dcx2.getParagraphs() 意思是將dcx2文檔所有段落取出來用一個數組存放,再進行循環段落;通過XWPFParagraph newPara = outDoc.createParagraph();給新的文檔創建一個新的段落;給新的段落添加對應的樣式newPara.getCTP().setPPr(para.getCTP().getPPr());最后由于段落中會劃分不同的XWPFRun再進行循環設置文本的字體、大小、顏色、加粗、斜體、下劃線等格式。 官方api介紹
剛才對XWPFRun沒有進行很好的解釋,這里重新舉例說明下,例如以下標紅的段落
按照正常理解這一個段落內容應該是一個東西,其實在XWPF中會劃分為不同的XWPFRun,使用idea打斷點查看數據
可以看出它將一段文字劃分為不同模塊,為什么會這樣,在一段文字中也存在不同的區別,例如文字的字體,像下圖中“根據”“2023”屬于不同的字體,所以會劃分為不同的XWPFRun,理解這個概念后同理明白為什么一個段落會劃分為29模塊
在前面其實已經實現第一個模塊最終效果的docx文檔合并的功能,所以在這個模塊講解在實現這個過程中記錄有意思的內容。
接著上面講XWPFRun這個函數,XWPFRun用于在 Word 文檔中添加或修改單個本 Run 或 Run 中的文字格式。它是文本段落(XWPFParagraph)中的最小單元,用于精細控制文本的格式和樣式??梢允褂?XWPFRun 類的各種方法來設置文本的字體、大小、顏色、加粗、斜體、下劃線等格式。 下列是在使用過程中記錄的一些屬性,以及這些屬性對應能夠設置的格式注釋。
XWPFRun run = firstParagraph.createRun();
XWPFRun tempRun = xwpfRuns.get(i);
//默認:宋體(wps)/等線(office2016) 5號 兩端對齊 單倍間距
run.setText(tempRun.text());
//加粗
run.setBold(tempRun.isBold());
//我也不知道這個屬性做啥的
run.setCapitalized(tempRun.isCapitalized());
//設置顏色--十六進制
run.setColor(tempRun.getColor());
//這個屬性報錯
run.setCharacterSpacing(tempRun.getCharacterSpacing());
//浮雕字體----效果和印記(懸浮陰影)類似
run.setEmbossed(tempRun.isEmbossed());
//雙刪除線
run.setDoubleStrikethrough(tempRun.isDoubleStrikeThrough());
run.setEmphasisMark(tempRun.getEmphasisMark().toString());
//字體,//字體,范圍----效果不詳
run.setFontFamily(tempRun.getFontFamily());
//字體大小,沒有設置默認是-1,
if(tempRun.getFontSize() != -1){
run.setFontSize(tempRun.getFontSize());
}
//印跡(懸浮陰影)---效果和浮雕類似
run.setImprinted(tempRun.isImprinted());
//斜體(字體傾斜)
run.setItalic(tempRun.isItalic());
//字距調整----這個好像沒有效果
run.setKerning(tempRun.getKerning());
//陰影---稍微有點效果(陰影不明顯)
run.setShadow(tempRun.isShadowed());
//小型股------效果不清楚
run.setSmallCaps(tempRun.isSmallCaps());
//單刪除線(廢棄)
run.setStrike(tempRun.isStrike());
//單刪除線(新的替換Strike)
run.setStrikeThrough(tempRun.isStrikeThrough());
//下標(吧當前這個run變成下標)---枚舉
run.setSubscript(tempRun.getSubscript());
//設置兩行之間的行間距
run.setTextPosition(tempRun.getTextPosition());
//各種類型的下劃線(枚舉)
run.setUnderline(tempRun.getUnderline());
run.setVerticalAlignment(tempRun.getVerticalAlignment().toString());
run.setVanish(tempRun.isVanish());
run.setUnderlineThemeColor(tempRun.getUnderlineColor());
run.setUnderlineColor(tempRun.getUnderlineColor());
run.setTextScale(tempRun.getTextScale());
run.setTextPosition(tempRun.getTextPosition());
run.setTextHighlightColor(tempRun.getTextHightlightColor().toString());
// run.setStyle(tempRun.gets); 沒找到這個屬性
run.setLang(tempRun.getLang());
XWPFParagraph 是 Apache POI 庫中 XWPF 模塊的一部分,用于創建或修改 Word 文檔中的段落。它可以添加不同的文本格式,并且可以添加圖片、表格、超鏈接等內容。XWPFParagraph 類可以控制段落的樣式和格式,包括字體、字號、行距、首行縮進、對齊方式等。可以使用 XWPFParagraph 類的各種方法來設置段落的格式和樣式。
常用方法:
//創建一個新的 XWPFRun 對象,用于在段落中添加文本或修改文本格式。
createRun()
//設置段落的對齊方式,align 參數可以是 LEFT、CENTER、RIGHT、JUSTIFY 等值。
setAlignment(ParagraphAlignment align)
//設置段落的行距和行距規則,lineSpacing 參數是行距大?。ㄒ园鯙閱挝唬?,lineSpacingRule 參數可以是 EXACT、AT_LEAST、AUTO 等值。
setSpacingBetween(int lineSpacing, LineSpacingRule lineSpacingRule)
//設置段落的左縮進大?。ㄒ园鯙閱挝唬?。
setIndentationLeft(int indentation)
//設置段落的右縮進大?。ㄒ园鯙閱挝唬? setIndentationRight(int indentation)
//設置段落的編號 ID。
setNumID(BigInteger numId)
//設置段落的編號格式,numFmt 參數可以是 DECIMAL、LOWERCASE_LETTER、UPPERCASE_LETTER 等值。
setNumFmt(NumberFormat numFmt)
//在段落中添加圖片,pictureType 參數是圖片類型,filename 參數是圖片文件名,width 和 height 參數是圖片寬度和高度。
createPicture(XWPFRun run, int pictureType, String filename, int width, int height)
其他方法:
//指定應顯示在左邊頁面指定段周圍的邊界。
setBorderBottom(Borders.APPLES);
//指定應顯示在下邊頁面指定段周圍的邊界。
setBorderLeft(Borders.APPLES);
//指定應顯示在右側的頁面指定段周圍的邊界。
setBorderRight(Borders.ARCHED_SCALLOPS);
//指定應顯示上方一組有相同的一組段邊界設置的段落的邊界。這幾個是對段落之間的格式的統一,相當于格式刷
setBorderTop(Borders.ARCHED_SCALLOPS);
//---正文寬度會稍微變窄
p1.setFirstLineIndent(99);
//---段落的對齊方式 1左 2中 3右 4往上 左 不可寫0和負數
p1.setFontAlignment(1);
//---首行縮進,指定額外的縮進,應適用于父段的第一行。
p1.setIndentationFirstLine(400);
//---首行前進,指定的縮進量,應通過第一行回到開始的文本流的方向上移動縮進從父段的第一行中刪除。
p1.setIndentationHanging(400);
//---整段右移
p1.setIndentFromLeft(400);
//--此方法提供了樣式的段落,這非常有用。
p1.setStyle("");
//--此元素指定是否消費者應中斷超過一行的文本范圍,通過打破這個詞 (打破人物等級) 的兩行或通過移動到下一行 (在詞匯層面上打破) 這個詞的拉丁文字。
p1.setWordWrapped(true);
//---指定的文本的垂直對齊方式將應用于此段落中的文本
p1.setVerticalAlignment(TextAlignment.CENTER);
//--指定行之間的間距如何計算存儲在行屬性中。
p1.setSpacingLineRule(LineSpacingRule.AT_LEAST);
//--指定應添加在此線單位在文檔中的段落的第一行之前的間距。
p1.setSpacingBeforeLines(6);
//--指定應添加上面這一段文檔中絕對單位中的第一行的間距。
p1.setSpacingBefore(6);
//--指定應添加在此線單位在文檔中的段落的最后一行之后的間距。
p1.setSpacingAfterLines(6);
//--指定應添加在文檔中絕對單位這一段的最后一行之后的間距。
p1.setSpacingAfter(6);
//--指定當渲染此分頁視圖中的文檔,這一段的內容都呈現在文檔中的新頁的開始。
p1.setPageBreak(true);
剛在展示活動內容都是根據自身的需求寫小demo,實際項目遠遠不止這些內容,其中還是存在不足之處,例如word中的表格、圖片都是需要單獨處理,表格有個專門類XWPFTable;圖片也有XWPFPictureData、 XWPFPicture,
Apache POI 官方網站提供了完整的 API 文檔:poi.apache.org/apidocs/dev…
Apache POI 的 GitHub 倉庫中查看示例代碼和文檔:github.com/apache/poi
Java POI 生成Word文檔:blog.csdn.net/qq_34755766…
Apache POI 中文版download.csdn.net/download/qq…
作者:沐游虞
轉自:https://juejin.cn/post/7237487091554730021
來源:稀土掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。