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

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

模版文件

生成文件

java 复制代码
    /**
     * 直接下载启动会预约单
     * @param id
     * @param response
     */
    @Override
    public 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;
    }
java 复制代码
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 + 时间戳 + .docx
            String 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;
    }
}
相关推荐
青浅l2 小时前
vue中回显word、Excel、txt、markdown文件
vue.js·word·excel
开心星人2 小时前
Leetcode hot100 Java刷题(二)
java·算法·leetcode
喵手2 小时前
Java与Microservices架构的结合:构建可扩展、高可用的系统!
java·架构·华为云
半部论语2 小时前
MyBatis-Plus 通用 CRUD 实现原理技术文档
java·spring boot·mybatis
手握风云-2 小时前
JavaEE 进阶第六期:Spring Boot 如何重塑后端开发
java·spring boot·java-ee
Deamon Tree3 小时前
【设计题】如何实现一个线程安全的缓存?
java·spring boot·spring
毕设源码-钟学长3 小时前
【开题答辩全过程】以 基于Java的相机专卖网的设计与实现为例,包含答辩的问题和答案
java·开发语言
CodeAmaz3 小时前
Zookeeper 分布式锁实战版
java·分布式·后端·zookeeper
海域云SeaArea_3 小时前
CentOS7 单机安装 Zookeeper 3.5.8(JDK 1.8 环境)
java·zookeeper·java-zookeeper