java使用poi-tl模版+vform自定义表单生成word
java使用poi-tl模版+vform自定义表单生成word文件
模版文件

生成文件

/*** 直接下载启动会预约单* @param id* @param response*/@Overridepublic void exportAppointment(Long id, HttpServletResponse response) {TbProjectStart tbProjectStart = tbProjectStartMapper.selectTbProjectStartById(id);ProjectInitiation initiation = projectInitiationService.findById(tbProjectStart.getProjectInitiationId(), null);//获取数据Map<String, Object> data = getMapData(initiation, tbProjectStart);//模板选择String url = "classpath:templates/start_yuyue.docx";String fileName=initiation.getAcceptanceNumber().replace(" ", "")+ "_启动会预约确认单_" + DateUtils.dateTimeNow();
//// // 响应返回word文件PoiTlWord.generateWordRespose(response,url,data,resourceLoader,fileName + ".docx");
// //地址返回word文件
// String targetDirPath = RuoYiConfig.getUploadPath("/start/download/");
// String fileUrl=PoiTlWord.generateWordToPath(url,data,resourceLoader,fileName + ".docx",targetDirPath);
// System.out.println(fileUrl);//响应返回 pdf 文件//WordToPdf.generatePdfRespose(response,url,data,resourceLoader,libreoffice,fileName + ".pdf");
// //地址返回 pdf文件
// String targetDirPath = RuoYiConfig.getUploadPath("/start/download/");
// String fileUrl=WordToPdf.generatePdfToPath(url,data,resourceLoader,libreoffice,fileName + ".pdf",targetDirPath);
// System.out.println(fileUrl);}/*** 获取数据* @param initiation* @param tbProjectStart* @return*/private Map<String, Object> getMapData(ProjectInitiation initiation, TbProjectStart tbProjectStart) {Map<String, Object> data = new HashMap<>();data.put("projectName", initiation.getProjectName());//主要研究者+辅助研究着String masterInvestigator = initiation.getPrincipalInvestigator() +" "+ (initiation.getAuxiliaryInvestigator()!=null? initiation.getAuxiliaryInvestigator():"");data.put("masterInvestigator", masterInvestigator);//申办者data.put("sponsorName", initiation.getSponsorName());data.put("craNames", initiation.getCraNames());data.put("crcNames", initiation.getCrcNames());//签字时间data.put("signData","");if(tbProjectStart.getJgApprovalTime()!=null){data.put("signData",DateUtils.parseDateToStr("yyyy-MM-dd",tbProjectStart.getJgApprovalTime()));}//获取自定义表单内容WfForm wfForm1=formService.queryById(9L);JSONObject modelJson=JSONObject.parseObject(wfForm1.getContent());Map<String,JSONObject> formMap=new HashMap<>();PoiTlWord.getWidgetListMap(formMap,modelJson.getString("widgetList"));//获取自定义表单数据TbProjectStartVform tbProjectStartVform = new TbProjectStartVform();tbProjectStartVform.setStartId(tbProjectStart.getId());List<TbProjectStartVform> tbProjectStartVforms = tbProjectStartVformMapper.selectList(tbProjectStartVform);List<TemplateTemp> templateList=new ArrayList<>();tbProjectStartVforms.forEach(s -> {if(s.getDataValue()!=null){TemplateTemp temp = new TemplateTemp();temp.setName(formMap.get(s.getDataName()).getString("label"));StringBuilder value= new StringBuilder();if(s.getDataType().equals("radio")||s.getDataType().equals("checkbox")){//单选 复选框JSONArray optionItems=formMap.get(s.getDataName()).getJSONArray("optionItems");for(int i=0;i<optionItems.size();i++){JSONObject optionItem=optionItems.getJSONObject(i);System.out.println(s.getDataValue()+"---"+optionItem.get("value")+"-----"+optionItem.getString("label")+"---"+s.getDataLabel());value.append(s.getDataValue().equals(optionItem.get("value").toString()) ? " ☒" : " ☐").append(optionItem.getString("label"));}}else if(s.getDataType().equals("picture-upload")){//图片上传的处理JSONArray jsonArray=JSONArray.parseArray(s.getDataValue());List<Map<String, PictureRenderData>> list = new ArrayList<>();for(int i=0;i<jsonArray.size();i++){JSONObject jsonObject1=jsonArray.getJSONObject(i);System.out.println(jsonObject1.getString("url"));list.add(PoiTlWord.createPictureMap(profile,jsonObject1.getString("url"),100,100));}temp.setImgs(list);}else {value.append(s.getDataValue());}temp.setValue(value.toString());templateList.add(temp);}});data.put("templateList", templateList);return data;}
public class PoiTlWord {/*** 生成 Word 并保存到指定路径,返回文件访问 URL(或完整路径)* @param url 模板路径(如 classpath:templates/xxx.docx)* @param data 模板渲染数据* @param resourceLoader Spring 资源加载器* @param fileStr 自定义 Word 文件名(不含路径,如 "启动会预约单";若为 null 则自动生成)* @param targetDirPath Word 保存的目标目录(如 "D:/word/export" 或 "/data/word/export")* @return 生成的 Word 文件完整访问 URL(如 "file:///D:/word/export/启动会预约单_20251106.docx")*/public static String generateWordToPath(String url, Map<String, Object> data,ResourceLoader resourceLoader, String fileStr,String targetDirPath) {// ========== 1. 初始化目标目录和文件名 ==========// 校验并创建目标目录(不存在则自动创建多级目录)File targetDir = new File(targetDirPath);if (!targetDir.exists()) {boolean mkdirsSuccess = targetDir.mkdirs();if (!mkdirsSuccess) {throw new RuntimeException("创建 Word 目标目录失败:" + targetDirPath);}}// 生成最终 Word 文件名(处理后缀和唯一性)String finalWordName;if (fileStr == null || fileStr.trim().isEmpty()) {// 自动生成唯一文件名:UUID + 时间戳 + .docxString uniqueId = UUID.randomUUID().toString().replace("-", "");String timestamp = String.valueOf(System.currentTimeMillis());finalWordName = "Word_" + uniqueId + "_" + timestamp + ".docx";} else {// 自定义文件名:补充 .docx 后缀(若未带)finalWordName = fileStr.endsWith(".docx") ? fileStr : fileStr + ".docx";}// 最终 Word 文件完整路径File finalWordFile = new File(targetDir, finalWordName);// ========== 2. 加载模板并生成 Word 到目标路径 ==========Resource resource = resourceLoader.getResource(url);try (InputStream inputStream = resource.getInputStream();OutputStream out = new FileOutputStream(finalWordFile);BufferedOutputStream bos = new BufferedOutputStream(out)) {// poi-tl 编译模板并渲染数据Configure config = Configure.builder().build();XWPFTemplate template = XWPFTemplate.compile(inputStream, config).render(data);// 写入目标文件template.write(bos);bos.flush();PoitlIOUtils.closeQuietly(template);System.out.println("Word 生成成功,保存路径:" + finalWordFile.getAbsolutePath());// ========== 3. 生成并返回文件访问 URL ==========// 格式1:本地文件 URL(如 file:///D:/word/export/xxx.docx 或 file:///data/word/export/xxx.docx)
// String fileUrl = finalWordFile.toURI().toString();// 格式2:仅返回绝对路径(若不需要 URL 格式,直接返回 finalWordFile.getAbsolutePath())return finalWordFile.getAbsolutePath();// return fileUrl;} catch (Exception e) {e.printStackTrace();throw new RuntimeException("生成 Word 失败:" + e.getMessage(), e);}}/*** word写入响应中* @param response* @param url* @param data*/public static void generateWordRespose(HttpServletResponse response, String url, Map<String, Object> data, ResourceLoader resourceLoader, String fileStr) {String fileName = URLEncoder.encode(fileStr, StandardCharsets.UTF_8);// 设置响应头response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");response.setHeader("Content-Disposition", "attachment;filename=" + fileName);org.springframework.core.io.Resource resource = resourceLoader.getResource(url);// 获取输入流try (InputStream inputStream = resource.getInputStream()) {// 使用输入流加载模板Configure config = Configure.builder().build();XWPFTemplate template = XWPFTemplate.compile(inputStream,config).render(data);OutputStream out = response.getOutputStream();BufferedOutputStream bos = new BufferedOutputStream(out);template.write(bos);bos.flush();out.flush();PoitlIOUtils.closeQuietlyMulti(template, bos, out);}catch (Exception e){e.printStackTrace();}}/*** 解析自定义表单* @param map* @param widgetList*/public static void getWidgetListMap(Map<String, JSONObject> map, String widgetList){JSONArray jsonArray=JSONArray.parseArray(widgetList);for(int i=0;i<jsonArray.size();i++){JSONObject jsonObject=jsonArray.getJSONObject(i);
// System.out.println(jsonObject);if(jsonObject.containsKey("widgetList")){getWidgetListMap(map,jsonObject.getString("widgetList"));}else{String type=jsonObject.getString("type");JSONObject options=jsonObject.getJSONObject("options");String id = options.getString("name");String label=options.getString("label");JSONArray optionItems=options.getJSONArray("optionItems");JSONObject temp=new JSONObject();temp.put("type",type);temp.put("label",label);temp.put("optionItems",optionItems);map.put(id,temp);}}}public static Map<String, PictureRenderData> createPictureMap(String profile, String pictureName, int width, int height) {Map<String, PictureRenderData> map = new HashMap<>();//创建PictureRenderData对象并设置其大小//Pictures还有其他方法,如Pictures.ofStream()流处理,可根据自己的需求及文档替换if(pictureName.startsWith("http")){try {System.out.println(pictureName);URL url = new URL(pictureName);// 发起HTTP请求获取连接(注意:此处不需要try-with-resources,改为手动关闭)HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(5000); // 连接超时时间conn.setReadTimeout(5000); // 读取超时时间// 检查请求是否成功(状态码200表示成功)if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {// 使用远程流创建图片 .size(width, height)map.put("picture", Pictures.ofStream(conn.getInputStream()).create());} else {throw new RuntimeException("远程图片获取失败,状态码:" + conn.getResponseCode());}// 手动关闭连接资源conn.disconnect();} catch (Exception e) {e.printStackTrace(); // 捕获网络异常、连接超时等问题}}else if(pictureName.startsWith("/dev-api")){// 2. 拼接完整路径:根路径 + 上传子目录 + 相对路径// 示例:/opt/gcp-java/file + /profile/upload + /2025/11/06/xxx.png = 完整路径File fullPathFile = new File(profile + pictureName.replace("/dev-api/profile",""));String fullPath = fullPathFile.getAbsolutePath();InputStream inputStream = null;try {// 3. 校验文件是否存在、是否为文件if (!fullPathFile.exists()) {throw new RuntimeException("图片文件不存在:" + fullPath);}if (!fullPathFile.isFile()) {throw new RuntimeException("路径不是文件:" + fullPath);}// 4. 校验文件权限(读权限)if (!fullPathFile.canRead()) {throw new RuntimeException("图片文件无读权限:" + fullPath + ",请赋予读权限(chmod +r 文件名)");}int targetWidth=600;// 4. 读取图片原始尺寸(关键:用于比例计算)inputStream = new FileInputStream(fullPathFile);BufferedImage originalImage = ImageIO.read(inputStream); // 解析图片获取原始尺寸int originalWidth = originalImage.getWidth(); // 原始宽度int originalHeight = originalImage.getHeight(); // 原始高度// 5. 计算自适应高度(核心公式:目标高度 = 原始高度 * 目标宽度 / 原始宽度)int targetHeight = (originalHeight * targetWidth) / originalWidth;System.out.println("图片原始尺寸:" + originalWidth + "x" + originalHeight+ ",自适应后尺寸:" + targetWidth + "x" + targetHeight);// 6. 重新读取流(ImageIO.read会消耗流,需重新打开)+ 等比缩放inputStream.close(); // 关闭已消耗的流inputStream = new FileInputStream(fullPathFile); // 重新打开流PictureRenderData renderData = Pictures.ofStream(inputStream).size(targetWidth, targetHeight) // 传入100%宽度 + 比例高度.create();map.put("picture", renderData);System.out.println("图片读取并等比缩放成功:" + fullPath);} catch (Exception e) {System.err.println("图片读取失败:" + e.getMessage());e.printStackTrace();} finally {// 6. 关闭流资源(避免内存泄漏)if (inputStream != null) {try {inputStream.close();} catch (Exception e) {e.printStackTrace();}}}}return map;}
}