需求:
从数据库中查询体检卡的调拨记录,生成word,转成pdf,返回给前端,客户可以选择打印或者下载
这里使用poi把数据转成word,在使用LibreOffice把word转成pdf
至于为什么不直接制作pdf模板,是因为公司不想花钱买模板软件,所以选择的这个方案
效果图:
1.poi填充wrod工具的类:实现动态生成表格+文本替换
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.context.annotation.Configuration;
import java.io.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* poi生成word 并且动态生成表格
* 参数1:word模板路径"D:\\ok\\体检卡出库单.docx" templatePath
* 参数2:文本替换map,key是word字段,value是替换的值 param
* 参数3,动态表格的list数据 list
* 参数4:textList,中文表头集合list textList
* 参数5:word输出的位置+文件名 outPath
*/
@Configuration
public class PoiDocUtils {
public static void toDoc(String templatePath ,Map<String,String>param, List<Map<String, String>> list, List<String>textList,String outPath ) {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(templatePath);
XWPFDocument doc = new XWPFDocument(in);
List<XWPFParagraph> allXWPFParagraphs = doc.getParagraphs();
for (XWPFParagraph xwpfParagraph : allXWPFParagraphs) {
List<XWPFRun> runs = xwpfParagraph.getRuns();
for (XWPFRun run : runs) {
String text = run.getText(0);
if (text != null && text != "") {
//指定位置开始创建表格 用able作为标记
if (text.equals("able")) {
//动态集合格式封装成list<map> 这里定义map的key集合
//把要转成表格的list数据的key取出来 集成在一个表头list中
List<String> keyList = new ArrayList<String>();
Map<String, String> map = list.get(0);
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
keyList.add(key);
}
//表的行数
int rows = list.size();
//表的列数
int cols = textList.size();
XmlCursor cursor = xwpfParagraph.getCTP().newCursor();
XWPFTable tableOne = doc.insertNewTbl(cursor);
//样式控制
CTTbl ttbl = tableOne.getCTTbl();
CTTblPr tblPr = ttbl.getTblPr() == null ? ttbl.addNewTblPr() : ttbl.getTblPr();
CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW();
CTJc cTJc = tblPr.addNewJc();
cTJc.setVal(STJc.Enum.forString("center"));//表格居中
tblWidth.setW(new BigInteger("9000"));//每个表格宽度
tblWidth.setType(STTblWidth.DXA);
//表格的表头创建,去上边textList表头集合的字段
XWPFTableRow tableRowTitle = tableOne.getRow(0);
tableRowTitle.getCell(0).setText(textList.get(0));
for (int i = 1; i < textList.size(); i++) {
tableRowTitle.addNewTableCell().setText(textList.get(i));
}
for (Map<String, String> cardMap : list) {
//遍历list 得到要导出的每一行表格数据,数据结构是map
XWPFTableRow createRow = tableOne.createRow();
for (int i = 0; i < keyList.size(); i++) {
String s = cardMap.get(keyList.get(i));
createRow.getCell(i).setText(s);
}
}
run.setText("", 0);
} else {
for (Map.Entry<String, String> entry : param.entrySet()) {
String key = entry.getKey();
int i = text.indexOf(key);
if (text.indexOf(key) != -1) {
text = text.replace(key, entry.getValue());
run.setText(text, 0);
}
}
}
}
}
}
out = new FileOutputStream(outPath);
// 输出
doc.write(out);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.LibreOffice工具类实现word转pdf
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
/**
* office文件转pdf工具类
*/
@Configuration
public class LibreOfficePDFUtils {
/**
* 利用libreOffice将office文档转换成pdf
* @param inputFile doc文件全路径 : 文件夹+文件名
* @param pdfFile 保存输出的文件夹名
* @return
*/
public static boolean convert2PDF(String inputFile, String pdfFile){
String command;
boolean flag;
String osName = System.getProperty("os.name");
System.out.println(osName);
if (osName.contains("Windows")) {
command = "cmd /c start soffice --headless --invisible --convert-to pdf:writer_pdf_Export " + inputFile + " --outdir " + pdfFile;
}else {
command = "libreoffice --headless --invisible --convert-to pdf:writer_pdf_Export " + inputFile + " --outdir " + pdfFile;
}
flag = executeCommand(command);
return flag;
}
/**
* 执行command指令
* @param command
* @return
*/
public static boolean executeCommand(String command) {
Process process;// Process可以控制该子进程的执行或获取该子进程的信息
try {
process = Runtime.getRuntime().exec(command);// exec()方法指示Java虚拟机创建一个子进程执行指定的可执行程序,并返回与该子进程对应的Process对象实例。
// 下面两个可以获取输入输出流
// InputStream errorStream = process.getErrorStream();
// InputStream inputStream = process.getInputStream();
} catch (IOException e) {
return false;
}
int exitStatus = 0;
try {
exitStatus = process.waitFor();// 等待子进程完成再往下执行,返回值是子线程执行完毕的返回值,返回0表示正常结束
System.out.println(exitStatus);
// 第二种接受返回值的方法
int i = process.exitValue(); // 接收执行完毕的返回值
} catch (InterruptedException e) {
return false;
}
if (exitStatus != 0) {
} else {
}
process.destroy(); // 销毁子进程
return true;
}
3.文件存储的服务器地址
application-dev.yml设置 file: docPath: D:/pdfData/
@Component
@ConfigurationProperties(prefix="file")
@Data
public class FileUploadConfig {
//库存管理调拨记录的doc存储路径
private String docPath;
}
4.代码实例
controller
/**
* 库存管理-打印调拨记录
* @return
*/
@PostMapping("/printAllocationRecord")
public void printAllocationRecord(@RequestParam Long allocationRecordId ,HttpServletRequest request,HttpServletResponse response){
try {
//Long allocationRecordId = purchaseStockAllocationVO.getAllocationRecordId();
reservationPhysicalCardMakeService.printAllocationRecord(allocationRecordId,request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 库存管理-打印调拨记录
* @param allocationRecordId
* @return
*/
void printAllocationRecord(Long allocationRecordId, HttpServletRequest request,HttpServletResponse response);
/**
* 库存管理-打印调拨记录
*
* @param allocationRecordId
* @return
*/
@Override
public void printAllocationRecord(Long allocationRecordId, HttpServletRequest request, HttpServletResponse response) {
//根据前端的id查询数据库
QueryWrapper<PurchaseAllocationRecord> purchaseAllocationRecordQueryWrapper = new QueryWrapper<>();
purchaseAllocationRecordQueryWrapper.eq("par.id", allocationRecordId);
//得到需要导出的结果
List<StorageManageVO> result = purchaseAllocationRecordMapper.getResultList(purchaseAllocationRecordQueryWrapper);
//初始化序号0,因为数据库没有这个序号字段,导出的pdf表格需要序号
AtomicReference<Integer> number = new AtomicReference<>(0);
//处理数据,得到word表格中的数据.list里是map
List<Map<String, String>> list = result.stream().map(storageManageVO -> {
//体检卡类型(0电子卡 1纸质卡)
String physicalExaminationCardType = storageManageVO.getPhysicalExaminationCardType();
if ("0".equals(physicalExaminationCardType)) {
physicalExaminationCardType = "电子体检卡";
} else {
physicalExaminationCardType = "纸质体检卡";
}
//制卡方式(0:规则制卡 1:导入制卡)
String makeCardWay = storageManageVO.getMakeCardWay();
if ("0".equals(makeCardWay)) {
makeCardWay = "规则制卡";
} else {
makeCardWay = "导入制卡";
}
Map<String, String> map = new LinkedHashMap<>();
map.put("number", number.toString());
map.put("physicalExaminationCardNo", storageManageVO.getPhysicalExaminationCardNo());
map.put("productName", storageManageVO.getProductName());
map.put("physicalExaminationYear", storageManageVO.getPhysicalExaminationYear());
map.put("physicalExaminationCardType", physicalExaminationCardType);
map.put("makeCardWay", makeCardWay);
number.getAndSet(number.get() + 1);
return map;
}).collect(Collectors.toList());
//读取word模板的绝对路径
String templatePath = this.getClass().getClassLoader().getResource("templates/diaobodan.docx").getPath();
//设置word中的文本替换集合
Map<String, String> param = new HashMap<>();
param.put("inAllocationName", result.get(0).getInAllocationName());
param.put("outAllocationName", result.get(0).getOutAllocationName());
param.put("createUser", result.get(0).getCreateUser());
param.put("allocationCount", result.get(0).getAllocationCount().toString());
param.put("createTime", result.get(0).getCreateTime());
//设置中文表头数据
List<String> textList = new ArrayList<String>();
textList.add("序号");
textList.add("体检卡号");
textList.add("产品名称");
textList.add("体检年度");
textList.add("体检卡类型");
textList.add("制卡方式");
//服务器存储的文件夹地址 wind :D:/pdfData/
String docPath = fileUploadConfig.getDocPath();
//文件名 = 当前时间+uuid:4566-2342-22ee-4432,去掉横杠
String fileName = LocalDate.now()+"-"+ UUID.randomUUID().toString().replaceAll("-", "");
//doc保存全路径: 文件夹位置+文件名
String docUrl = docPath + fileName + ".docx";
//使用poi工具类,生成doc,第一个参数word模板的绝对路径,第二个参数,文本替换集合,第三个参数:word表格中的数据,第四个参数中文表头集合,第四个参数doc保存的全路径
PoiDocUtils.toDoc(templatePath, param, list, textList, docUrl);
System.out.println("doc生成完成");
//利用libreOffice将office文档转换成pdf 参数一doc文件全路径 ; 参数二要保存的文件夹
boolean b = LibreOfficePDFUtils.convert2PDF(docUrl, docPath);
if (!b){
throw new RuntimeException("doc转换pdf失败,LibreOffice异常");
}else {
//pdf保存全路径
String pdfUrl = docPath + fileName + ".pdf";
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// ---------------把pdf转成流给前端,可以实现下载和打印---------------------------
//把pdf文件的全路径转成file文件
File pdfFile = new File(pdfUrl);
//获取pdf文件的全路径
String pdfSource = pdfFile.getPath();
OutputStream outputStream = null;
try {
FileInputStream hFile = new FileInputStream(pdfSource); // 以byte流的方式打开文件
int i = hFile.available(); // 得到文件大小
byte data[] = new byte[i];
hFile.read(data); // 读数据
hFile.close();
response.reset();
response.resetBuffer();
response.setContentType("application/pdf;charset-UTF-8"); // 设置返回的文件类型
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String name = URLEncoder.encode(time+".pdf","UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" +name);
outputStream = response.getOutputStream(); // 得到向客户端输出二进制数据的对象
outputStream.write(data); // 输出数据
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
outputStream.flush();
outputStream.close();
} catch (Exception ex) {
}
}
}
}
5.word模板