一、需求
總體需求是根據word模闆和資料,生成對應的word檔案。經過技術調查後,确定poi-tl是最合适,最友善的架構。于是參考了官方文檔和一些文章,很快就掌握了基本用法,這裡附一下官方文檔,寫的清晰透徹,簡單明了。
官方文檔:http://deepoove.com/poi-tl/#
源碼:https://github.com/Sayi/poi-tl
但是,我的需求中有一個動态生成帶小标題的表格資料的問題,如圖:
如圖中紅色框中就是動态的資料,然後綠色框中就是小标題,小标題和下面的資料都需要根據傳來的資料,動态的生成。
二、分析
一開始參考了官方文檔裡的例子,根據資料,動态的生成行和列。這種方式資料是可以動态顯示了,但是會出現列和表頭不對齊的問題。原因是因為模組化闆的時候,表格整體是使用拆分單元格的方式建的,導緻表格真實的列數和看到的列數不一樣。如圖所示:
如上圖,我們把word檔案複制到excel中,就會發現表格總共有11個列。是以,我們可以在生成資料行時,每行生成11個列,然後按照上一行的樣式,把列再進行合并。這個思路是沒問題,我也實作了,但是和我的需求還是有點不符合。因為這種方法需要知道模闆的全部列數,以及模闆中每個單元格所占的列數,這兩個參數我是取不到的。
于是我又參考了文檔裡區塊對的例子,發現如果把小标題和下面的記錄作為一個循環單元,進行動态的循環,最後形成的結果拼起來就是需求裡滿足的樣子。模闆如下:
三、代碼
這裡為了省事,我就把我的接口和測試類直接粘過來了,可以自己做個記錄,如果能幫到需要的人,給出一些啟發就更好了。
/**
* 生成報表的接口
* @param file word模闆
* @param
* @param response
* @param data
* @return
* @throws IOException
*/
@RequestMapping(value = "/getReport", method = RequestMethod.POST)
public String doCreateWordByTemplateAndData(MultipartFile file ,String type,HttpServletResponse response,@RequestParam("data") String data) throws IOException {
Map<String,Object> renderData = JSONObject.parseObject(data);
//增加對條形碼的處理
System.out.println(data);
Set<String> sets = renderData.keySet();
for (String key :sets) {
//條形碼
if(key.endsWith("barCode")){
FileInputStream barCode = ImgRenderUtil.getBarCode((String) renderData.get(key));
renderData.put(key,new PictureRenderData(150,70, ".png",barCode));
}
//圖檔
if(key.endsWith("img")){
FileInputStream imgByBase64 = ImgRenderUtil.getImgByBase64((String) renderData.get(key));
renderData.put(key,new PictureRenderData(150,70, ".png",imgByBase64));
}
if("cycleDatas".equals(key)){
List<Map> list = (List<Map>)renderData.get(key);
for (Map tableData : list) {
for (String tableDataKey : (Set<String>)tableData.keySet()) {
//清單項
if (tableDataKey.endsWith("_list")){
List<String> tableDataList = (List<String>)tableData.get(tableDataKey);
ArrayList<TextRenderData> textRenderDatas = new ArrayList<>();
for (String str :tableDataList) {
textRenderDatas.add(new TextRenderData(str));
}
tableData.put(tableDataKey, new NumbericRenderData(textRenderDatas));
}
}
}
}
//清單項
if (key.endsWith("_list")){
List<String> list = (List<String>)renderData.get(key);
ArrayList<TextRenderData> textRenderDatas = new ArrayList<>();
for (String str :list) {
textRenderDatas.add(new TextRenderData(str));
}
renderData.put(key, new NumbericRenderData(textRenderDatas));
}
}
//根據類型選擇渲染方式類 如果遇到特殊的模闆無法直接渲染 可以自定義渲染的政策類
RenderPolicy policy = null;
if("1".equals(type)){//普通表格列
policy = new HackLoopTableRenderPolicy();
}
Configure config = Configure.newBuilder()
.bind("tableDatas", policy).build();
XWPFTemplate template = XWPFTemplate.compile(file.getInputStream(), config).render(renderData);
System.out.println(renderData);
ServletOutputStream outputStream = response.getOutputStream();
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename="
.concat("result.docx"));
try {
template.write(outputStream);
outputStream.flush();
outputStream.close();
template.close();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
調用接口的測試方法:
/**
* 測試的方法 遠端調用生成報表的接口
* @param response
* @return
* @throws IOException
*/
@RequestMapping(value = "/doTest5", method = RequestMethod.GET)
public String doTest5(HttpServletResponse response) throws IOException {
List<Map> cycle = new ArrayList<Map>();
List<Map> datas1 = new ArrayList<Map>();
Map<String,Object> map0 = new HashMap<String, Object>();
map0.put("childIndex",1.1);
map0.put("checkName","啟動會在獲得倫理批件和簽署協定之後");
map0.put("yes","");
map0.put("no","");
map0.put("NA","");
map0.put("problem","");
datas1.add(map0);
Map<String,Object> map1 = new HashMap<String ,Object>();
map1.put("index",1);
map1.put("childTitle","啟動會");
map1.put("tableDatas",datas1);
List<Map> datas2 = new ArrayList<Map>();
Map<String,Object> map01 = new HashMap<String, Object>();
map01.put("childIndex",2.1);
map01.put("checkName","研究者檔案夾齊全");
map01.put("yes","");
map01.put("no","");
map01.put("NA","");
map01.put("problem","");
datas2.add(map01);
Map<String,Object> map3 = new HashMap<String ,Object>();
map3.put("index",2);
map3.put("childTitle","研究者檔案夾");
map3.put("tableDatas",datas2);
List<Map> datas3 = new ArrayList<Map>();
Map<String,Object> map03 = new HashMap<String, Object>();
map03.put("childIndex",3.1);
map03.put("checkName","使用的試驗方案也倫理準許的版本一緻(填寫試驗方案版本号)");
map03.put("yes","");
map03.put("no","");
map03.put("NA","");
map03.put("problem","");
datas3.add(map03);
Map<String,Object> map06 = new HashMap<String ,Object>();
map06.put("childIndex",3.2);
map06.put("checkName","使用的知情同意書與倫理準許的版本一緻(請注明知情同意書版本号))");
map06.put("yes","");
map06.put("no","");
map06.put("NA","");
map06.put("problem","");
datas3.add(map06);
Map<String,Object> map5 = new HashMap<String ,Object>();
map5.put("index",3);
map5.put("childTitle","試驗方案、知情同意書、研究病曆和CRF");
map5.put("tableDatas",datas3);
cycle.add(map1);
cycle.add(map3);
cycle.add(map5);
Map<String,Object> renderMap = new HashMap<String, Object>();
renderMap.put("cycleDatas",cycle);
renderMap.put("title","北京某某醫院臨床試驗品質檢查報告");
renderMap.put("tableName","2015年度1季度器械試驗臨時檢查計劃");
renderMap.put("num","2020-017");
renderMap.put("trialName","xxx的臨床研究");
renderMap.put("member","xxx");
renderMap.put("projectNum","2015-118");
renderMap.put("shenbanzhe","xxx有限公司");
renderMap.put("checkNum","0");
renderMap.put("joinNum","0");
renderMap.put("doingNum","0");
renderMap.put("finishNum","0");
renderMap.put("stopNum","0");
Gson gson = new Gson();
Map<String, String> params = new HashMap<>();
params.put("data", gson.toJson(renderMap));
String fileUrl = "xxx.docx";
params.put("type","1");
File wordTemplate = new File(fileUrl);
String postUrl = "http://192.168.131.51:8081/report/getReport";
CloseableHttpResponse postResponse = HttpClientUtil.doPostFileAndData(postUrl, wordTemplate, params);
InputStream inputStream = null;
inputStream = postResponse.getEntity().getContent();
int len = 0;
byte[] buffer = new byte[1024];
ServletOutputStream outputStream = response.getOutputStream();
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename="
.concat("result.docx"));
while ((len = inputStream.read(buffer,0,1024))!=-1){
outputStream.write(buffer,0,len);
}
outputStream.flush();
outputStream.close();
inputStream.close();
return "ok";
}
工具類:
/**
* 根據條形碼内容 傳回條碼檔案流
* @param data
* @return
* @throws IOException
*/
public static FileInputStream getBarCode(String data) throws IOException {
BarcodeSettings settings = new BarcodeSettings();
settings.setType(BarCodeType.Code_128);
settings.setData(data);
settings.setData2D(data);
//設定底部顯示文本
settings.setShowTextOnBottom(true);
settings.setShowText(true);
settings.setBarHeight(4);
BarCodeGenerator barCodeGenerator = new BarCodeGenerator(settings);
BufferedImage bufferedImage = barCodeGenerator.generateImage();
ImageIO.write(bufferedImage, "png", new File("Barcode.png"));
return new FileInputStream("Barcode.png");
}
/**
* 根據base64的圖檔編碼 獲得圖檔的檔案流
* @param base64Str
* @return
*/
public static FileInputStream getImgByBase64(String base64Str) {
String base64Img = base64Str.replace("data:image/png;base64,", "");
String base64ImgNew = base64Img.replace("data:image/jpg;base64,", "");
FileOutputStream fileOutputStream = null;
try{
byte[] bytes = new BASE64Decoder().decodeBuffer(base64ImgNew.trim());
fileOutputStream =new FileOutputStream("img.png");
fileOutputStream.write(bytes);
return new FileInputStream("img.png");
}catch (Exception e){
e.printStackTrace();
}finally {
try {
fileOutputStream.close();
}catch (IOException e){
e.printStackTrace();
}
}
return null;
}
舊系統可能會使用httpclient調用接口,方法如下:
/**
*
* @param url 接口位址
* @param file 模闆檔案
* @param param 資料參數
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static CloseableHttpResponse doPostFileAndData(String url,File file, Map<String,String> param) throws ClientProtocolException, IOException {
// 建立Httpclient對象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
try {
HttpPost httpPost = new HttpPost(url);
// 相當于<input type="file" name="file"/>
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addBinaryBody("file",new FileInputStream(file),ContentType.MULTIPART_FORM_DATA,file.getName());
for (Map.Entry<String,String> entry :param.entrySet()) {
String key = entry.getKey();
// 相當于<input type="text" name="userName" value=userName>
StringBody value = new StringBody(entry.getValue(), ContentType.create("text/plain", Consts.UTF_8));
builder.addPart(key, value);
}
HttpEntity entity = builder.build();
httpPost.setEntity(entity);
response = httpClient.execute(httpPost);
HttpEntity entity1 = response.getEntity();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("傳出檔案及資料是出現異常",e);
}
return response;
}
還可以使用restTemplate模拟表單送出,上傳檔案和資料,如下:
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("data", renderMap);
params.add("file", new FileSystemResource("xxx.docx"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(params, headers);
String url = "http://192.168.131.51:8081/report/getReport";
// String url = "http://localhost:8081/report/getReport";
ResponseEntity<Resource> httpResponse = restTemplate.exchange(url
, HttpMethod.POST, httpEntity, Resource.class);
if (httpResponse.getStatusCode().equals(HttpStatus.OK)) {
InputStream inputStream = null;
OutputStream outputStream = null;
inputStream = httpResponse.getBody().getInputStream();
outputStream = response.getOutputStream();
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename="
.concat("result.docx"));
int len = 0;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer,0,1024))!=-1){
outputStream.write(buffer,0,len);
}
outputStream.flush();
outputStream.close();
inputStream.close();
}