2.Java中根据需求选择性将业务数据导出到word文档中形成一本账的案例

在最近的工作中,遇到了一个这样的需求,就是用户要求在导出数据时,用户可以选择一条或者多条数据,并且连带着附件及其他信息同时导出到word文档中。

需求描述:

a、业务主数据

b、业务数据附属模块

此时主数据页面有导出功能,要求导出时,一个项目为一个word文档,然后填充所有的信息数据,包括附件,最终打包成zip压缩包返回给用户,实现效果如下:

实现过程

 1、添加pom依赖

<dependency>
  <groupId>org.docx4j</groupId>
  <artifactId>docx4j-JAXB-MOXy</artifactId>
  <version>8.3.13</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
</dependency>
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>5.2.5</version>
</dependency>
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.13.0</version>
</dependency>
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.8.32</version>
  <scope>compile</scope>
</dependency>

2、导出数据格式

@Data
public class ProjectData {

    @ApiModelProperty(value = "项目名称")
    private String projectName;

    @ApiModelProperty(value = "执行机构名称")
    private String bsoName;

    @ApiModelProperty(value = "机构简介")
    private String synopsis;

    @ApiModelProperty(value = "项目联系人")
    private String personName;

    @ApiModelProperty(value = "联系电话")
    private String personPhone;

    @ApiModelProperty(value = "项目开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai")
    private String projectStartTime;

    @ApiModelProperty(value = "项目结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai")
    private String projectEndTime;

    @ApiModelProperty(value = "实施区域【Y】")
    @AreaField(AreaFieldType.COLLABORATE_PROJECT_AREA)
    @Area(cascade = true)
    private List<String> admsAreaList;

    @ApiModelProperty(value = "实施区域集合【Y】")
    private String admsAreaListName;

    @ApiModelProperty(value = "经费预算(万元)")
    private String budget;

    @ApiModelProperty(value = "项目性质:1-公益创投;2-组织自有;3-组织间协作【Y】PROJECTNATURE")
    private String projectQuality;

    @ApiModelProperty(value = "项目状态:草稿箱、未开始、进行中、已结束【Y】")
    private String projectStatus;

    @ApiModelProperty(value = "服务领域")
    private String serviceArea;

    @ApiModelProperty(value = "项目背景")
    private String projectBackground;

    @ApiModelProperty(value = "预计产出")
    private String projectedOutputs;

    @ApiModelProperty(value = "服务对象集合")
    @MultiCode(CpServeObjectMapper.class)
    private List<String> cpServeObjectPoList;

    @ApiModelProperty(value = "服务对象")
    private String cpServeObjectPoListName;

    @ApiModelProperty(value = "服务人数")
    private String serveNum;

    @ApiModelProperty(value = "资助方")
    private String funderName;

    @ApiModelProperty(value = "资金来源")
    private String fundSource;

    @ApiModelProperty(value = "项目分类【Y】")
    @MultiCode(CpTypeMapper.class)
    private List<String> cpTypePoList;

    @ApiModelProperty(value = "项目分类集合")
    private String cpTypePoListName;

    @ApiModelProperty(value = "项目创新性")
    private String projectInnovation;

    @ApiModelProperty(value = "可遇见风险及应对措施")
    private String riskMeasure;

    @ApiModelProperty(value = "附件")
    @Attachment(AttachUseType.COLLABORATE_PROJECT_ATTACH)
    List<AttachmentVO> ossIdList;

    @ApiModelProperty(value = "项目评估报告")
    @Attachment(AttachUseType.COLLABORATE_PROJECT_ASS_REPORT)
    List<AttachmentVO> assReportList;

    @ApiModelProperty(value = "附件url集合")
    List<String> filePathList01;

    @ApiModelProperty(value = "项目评估报告url集合")
    List<String> filePathList02;

    @ApiModelProperty(value = "实施计划")
    private List<Plan> plans;

    @ApiModelProperty(value = "成果公开")
    private List<Achievement> achievements;

    @ApiModelProperty(value = "占位符")
    private String titlePlaceholder;
}
@Data
public class Plan {

    @ApiModelProperty(value = "实施计划")
    private String planName;

    @ApiModelProperty(value = "活动时间  开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private String startTime;

    @ApiModelProperty(value = "活动时间  结束")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private String endTime;

    @ApiModelProperty(value = "参与人数")
    private String numPeople;

    @ApiModelProperty(value = "活动形式")
    private String activityForm;

    @ApiModelProperty(value = "活动地点")
    private String location;

    @ApiModelProperty(value = "活动频次")
    private String activeFrequency;

    @ApiModelProperty(value = "活动目的")
    private String activeGoal;

    @ApiModelProperty(value = "备注")
    private String notes;

}
@Data
public class Achievement {

    @ApiModelProperty(value = "项目名称")
    private String projectName;

    @ApiModelProperty(value = "活动名称")
    private String title;

    @ApiModelProperty(value = "活动类型")
    private String activityType;

    @ApiModelProperty(value = "活动时间")
    private String activityTime;

    @ApiModelProperty(value = "详细地址")
    private String address;

    @ApiModelProperty(value = "活动区划【Y】")
    @AreaField(AreaFieldType.FEED_BACK_AREA)
    @Area(cascade = true)
    List<String> admsAreaList;

    @ApiModelProperty(value = "活动区划集合名称")
    private String admsAreaListName;

    @ApiModelProperty(value = "服务人次")
    private String personNumber;

    @ApiModelProperty(value = "捐赠金额")
    private String donationAmount;

    @ApiModelProperty(value = "服务时长")
    private String duration;

    @ApiModelProperty(value = "活动参与对象满意度")
    private String satisfaction;

    @ApiModelProperty(value = "活动效果说明")
    private String implementContent;

    @ApiModelProperty(value = "审核人")
    private String updateUser;

    @ApiModelProperty(value = "审核时间")
    private String updateTime;

    @ApiModelProperty(value = "佐证材料")
    @Attachment(AttachUseType.FEED_BACK_TYPE)
    List<AttachmentVO> attachmentPOList;

    @ApiModelProperty(value = "其他材料")
    @Attachment(AttachUseType.FEED_BACK_TYPE2)
    List<AttachmentVO> attachmentPOList2;

    @ApiModelProperty(value = "佐证材料附件url集合")
    private List<String> filePathList1;

    @ApiModelProperty(value = "其他材料附件url集合")
    private List<String> filePathList2;

}

3、文件生成工具类

package com.yinhai.kq.common.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ZipUtil;

import org.apache.poi.xwpf.usermodel.*;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.PartName;
import org.docx4j.openpackaging.parts.WordprocessingML.AlternativeFormatInputPart;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.relationships.Relationship;
import org.docx4j.wml.CTAltChunk;

import javax.xml.bind.JAXBException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import com.yinhai.kq.business.domain.export.ProjectData;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @date 2025/1/6 14:11
 */
public class GenerateOnWordTemplateKit {

    public static final String TEMPLATE_PATH = "项目地址+/src/main/resources/template/";
  
    public static final String FILE_PATH = TEMPLATE_PATH + "file/";
    public static final String OUTPUT_PATH = TEMPLATE_PATH + "output/";

    public static void main(String[] args) throws Exception {
        // 程序入口,主要作用是为多个项目分别根据模版组装数据后合并成一个word
        // 1. 准备数据(模拟从数据库中查询,key为项目名称,value为项目数据)
        Map<String, ProjectData> dataMap = queryData();
        // 2. 为每个项目生成一个以项目名为名称的文件夹,文件夹内包括word文档、附件
        // 2.1 创建一个临时文件夹
        String tempDirPath = FileUtil.mkParentDirs("temp/" + UUID.randomUUID()).getAbsolutePath();
        // 2.2 在这个临时文件夹下面,根据数据生成多个项目文件夹及word文档、附件
        generateAllWordDocument(dataMap, tempDirPath);
        // 3. 打包整个临时文件夹为zip压缩包
        File projectZipFile = compressIntoAZipFile(tempDirPath);
        System.out.println("压缩包路径保存:" + projectZipFile.getAbsolutePath());
        // 4. 删除所有临时文件
        deleteAllTempFiles(tempDirPath);
    }

    /**
     * 打包整个临时文件夹为zip压缩包
     *
     * @param tempDirPath 需要打包的文件夹目录
     * @return 压缩包文件
     */
    public static File compressIntoAZipFile(String tempDirPath) {
        return ZipUtil.zip(tempDirPath, OUTPUT_PATH + "慈善项目_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN) + ".zip");
    }

    /**
     * 删除所有临时文件
     *
     * @param tempDirPath 临时文件夹路径
     */
    public static void deleteAllTempFiles(String tempDirPath) {
        FileUtil.del(tempDirPath);
    }

    /**
     * 生成所有项目文档
     *
     * @param dataMap     所有项目信息,key为项目名称,value为项目数据
     * @param tempDirPath 临时目录,用于存储多个临时Word文档
     * @throws Exception 异常
     */
    public static void generateAllWordDocument(Map<String, ProjectData> dataMap, String tempDirPath) throws Exception {
        for (Map.Entry<String, ProjectData> entry : dataMap.entrySet()) {
            // 2.2 创建一个以项目名为名称的文件夹
            File projectDir = new File(tempDirPath + "/" + entry.getKey());
            if (!projectDir.exists() && !projectDir.mkdirs()) {
                throw new RuntimeException("文件夹[" + projectDir.getAbsolutePath() + "]创建失败");
            }
            // 2.3 为每个项目生成一个word文档
            ProjectData projectData = entry.getValue();
            generateWordDocument(projectData, projectDir);
        }
    }

    /**
     * 生成项目word文档
     *
     * @param projectData 项目信息
     * @param projectDir  临时目录,用于存储多个临时Word文档
     * @throws Exception 异常
     */
    private static void generateWordDocument(ProjectData projectData, File projectDir) throws Exception {
        // 创建一个列表,用于存储多个临时Word文档地址
        List<String> tempWordList = new ArrayList<>();

        tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_1.docx", projectData, projectDir));
        if (CollUtil.isNotEmpty(projectData.getFilePathList01())) {
            tempWordList.addAll(simulateInsertingAttachments(projectData.getFilePathList01(), projectDir));
        }
        if (CollUtil.isNotEmpty(projectData.getFilePathList02())) {
            tempWordList.addAll(simulateInsertingAttachments(projectData.getFilePathList02(), projectDir));
        }

        projectData.setTitlePlaceholder("二、实施计划");
        tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_3.docx", projectData, projectDir));

        if (CollUtil.isNotEmpty(projectData.getPlans())) {
            for (Plan plan : projectData.getPlans()) {
                tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_2.docx", plan, projectDir));
            }
        }

        projectData.setTitlePlaceholder("三、成果公开");
        tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_3.docx", projectData, projectDir));

        if (CollUtil.isNotEmpty(projectData.getAchievements())) {
            for (Achievement achievement : projectData.getAchievements()) {
                tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_4.docx", achievement, projectDir));
                if (CollUtil.isNotEmpty(achievement.getFilePathList1())) {
                    tempWordList.addAll(simulateInsertingAttachments(achievement.getFilePathList1(), projectDir));
                }
                if (CollUtil.isNotEmpty(achievement.getFilePathList2())) {
                    tempWordList.addAll(simulateInsertingAttachments(achievement.getFilePathList2(), projectDir));
                }
            }
        }

        mergeDocuments(projectData.getProjectName(), tempWordList, projectDir);

        // 删除临时文件
        tempWordList.forEach(FileUtil::del);
    }

    /**
     * 模拟从数据库中查询数据
     *
     * @return 返回一个模拟数据
     */
    private static Map<String, ProjectData> queryData() {
        Map<String, ProjectData> dataMap = new HashMap<>();
        for (int i = 1; i <= 2; i++) {
            // 创建模拟项目数据
            ProjectData project = new ProjectData();
            project.setProjectName("“友邻有爱”社区养老互助服务项目-" + i);

            List<Plan> plans = new ArrayList<>();
            for (int j = 1; j <= 3; j++) {
                Plan plan = new Plan();
                plan.setPlanName("计划-" + j);
                plans.add(plan);
            }
            project.setPlans(plans);

            List<Achievement> achievements = new ArrayList<>();
            for (int j = 1; j <= 2; j++) {
                Achievement achievement = new Achievement();
                achievement.setTitle("成果-" + j);
                // 附件地址,这里可以是文件key,配合download方法能下载到本地即可
                achievement.setFilePathList1(Arrays.stream(FileUtil.ls(FILE_PATH)).map(File::getAbsolutePath).collect(Collectors.toList()));
                achievement.setFilePathList2(Arrays.stream(FileUtil.ls(FILE_PATH)).map(File::getAbsolutePath).collect(Collectors.toList()));
                achievements.add(achievement);
            }
            project.setAchievements(achievements);

            dataMap.put(project.getProjectName(), project);
        }
        return dataMap;
    }

    /**
     * 生成word文档
     *
     * @param templatePath word模版路径
     * @param dataObj      数据对象
     * @param tempDirFile  临时目录
     * @return 临时文件路径
     * @throws Docx4JException 异常
     * @throws JAXBException   异常
     * @throws IOException     异常
     */
    private static String createWordMLPackage(String templatePath, Object dataObj, File tempDirFile) throws Docx4JException, JAXBException, IOException {
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File(templatePath));
        // 填充数据到模板
        Map<String, String> dataMap = objectToMap(dataObj);
        wordMLPackage.getMainDocumentPart().variableReplace(dataMap);
        // 保存新的Word文档
        String templateFileExtension = templatePath.substring(templatePath.lastIndexOf(".") + 1);
        File tempFile = new File(tempDirFile.getAbsolutePath() + "/" + UUID.randomUUID() + "." + templateFileExtension);
        if (!tempFile.exists() && !tempFile.createNewFile()) {
            throw new RuntimeException("文件[" + tempFile.getAbsolutePath() + "]创建失败");
        }
        wordMLPackage.save(tempFile);
        return tempFile.getAbsolutePath();
    }

    /**
     * 将对象转换为Map
     *
     * @param dataObj 对象
     * @return 返回一个Map
     */
    private static Map<String, String> objectToMap(Object dataObj) {
        if (null == dataObj) {
            return MapUtil.empty();
        }
        Map<String, String> dataMap = new HashMap<>();
        // 使用反射获取dataObj的所有字段
        Field[] fields = dataObj.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 设置访问权限,以便可以访问私有字段
            field.setAccessible(true);
            try {
                // 获取字段值
                Object value = field.get(dataObj);
                // 如果字段值是String类型,则添加到dataMap中
                if (value instanceof String) {
                    dataMap.put(field.getName(), (String) value);
                }
            } catch (IllegalAccessException e) {
                // 忽略无法访问的字段
            }
        }
        return dataMap;
    }

    /**
     * 合并文档
     *
     * @param fileName    合并之后的word文档名称
     * @param wordList    word列表
     * @param tempDirFile 临时目录
     * @throws Exception 异常
     */
    public static void mergeDocuments(String fileName, List<String> wordList, File tempDirFile) throws Exception {
        File tempFile = new File(tempDirFile.getAbsolutePath() + "/" + fileName + ".docx");
        if (!tempFile.exists() && !tempFile.createNewFile()) {
            throw new RuntimeException("文件[" + tempFile.getAbsolutePath() + "]创建失败");
        }
        // 保存合并后的文档
        mergeDoc(wordList, tempFile.getAbsolutePath());
    }

    /**
     * 模拟插入附件
     *
     * @param fileList    文件地址列表
     * @param tempDirFile 临时目录
     * @return 返回一个word文档列表地址
     * @throws Exception 异常
     */
    private static List<String> simulateInsertingAttachments(List<String> fileList, File tempDirFile) throws Exception {
        List<String> wordList = new ArrayList<>();

        // 创建一个新的Word文档
        XWPFDocument document = new XWPFDocument();

        // 创建一个段落
        XWPFParagraph paragraph = document.createParagraph();

        for (int i = 0; i < fileList.size(); i++) {
            String filePath = fileList.get(i);
            String fileName = getFileName(filePath);
            InputStream inputStream = downloadFile(filePath);
            String hyperlinkUrl = "./附件/" + fileName;

            FileUtil.writeFromStream(inputStream, new File(tempDirFile + hyperlinkUrl));

            // 超链接地址,可以是网络地址、本地文件相对路径
            XWPFHyperlinkRun hyperlinkRun = paragraph.createHyperlinkRun(hyperlinkUrl);
            hyperlinkRun.setText(fileName);
            hyperlinkRun.setColor("0000FF");
            // 设置下划线
            hyperlinkRun.setUnderline(UnderlinePatterns.SINGLE);

            // 插入一个换行,除了最后一次循环
            if (i < fileList.size() - 1) {
                paragraph.createRun().addBreak(BreakType.TEXT_WRAPPING);
            }
        }

        // 将文档保存到文件
        String tempFilePath = tempDirFile.getAbsolutePath() + "/" + UUID.randomUUID() + ".docx";
        try (FileOutputStream out = new FileOutputStream(tempFilePath)) {
            document.write(out);
        }

        // 关闭文档
        document.close();
        wordList.add(tempFilePath);
        return wordList;
    }

    /**
     * 从文件路径中提取文件名(包括扩展名)
     *
     * @param filePath 文件路径
     * @return 文件名(包括扩展名)
     */
    private static String getFileName(String filePath) {
        if (filePath == null || filePath.isEmpty()) {
            return null;
        }
        // 使用 Paths 来解析文件路径
        return Paths.get(filePath).getFileName().toString();
    }

    /**
     * 下载文件
     *
     * @param filePath 文件路径
     * @return 输入流
     * @throws IOException 异常
     */
    private static InputStream downloadFile(String filePath) throws IOException {
        // 这里应该是下载附件的逻辑,为了模拟效果直接从本地读取
        return Files.newInputStream(Paths.get(filePath));
    }

    /**
     * 下载图片到本地
     *
     * @param imageUrl 图片的URL
     * @param savePath 本地保存路径(包括文件名)
     * @throws IOException 如果发生I/O错误
     */
    public static void downloadImage(String imageUrl, String savePath) throws IOException {
        // 创建URL对象
        URL url = new URL(imageUrl);
        // 打开连接
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        // 允许输入流(默认为true)
        httpConn.setDoInput(true);
        // 不允许输出流(因为我们只是下载)
        httpConn.setDoOutput(false);
        // 连接服务器
        httpConn.connect();

        // 获取响应码
        int responseCode = httpConn.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) { // 成功
            // 获取输入流
            try (InputStream inputStream = httpConn.getInputStream();
                 OutputStream outputStream = new FileOutputStream(savePath)) {

                // 创建一个缓冲区
                byte[] buffer = new byte[4096];
                int bytesRead;

                // 读取数据并写入文件
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
        } else {
            throw new IOException("Failed to download image, HTTP response code: " + responseCode);
        }

        // 断开连接
        httpConn.disconnect();
    }

    /**
     * 合并word文件
     *
     * @param wordList    待合并文件集合
     * @param outFilePath 输出文件路径
     */
    public static void mergeDoc(List<String> wordList, String outFilePath) throws IOException, Docx4JException {
        List<InputStream> streamList = new ArrayList<>();
        for (String wordPath : wordList) {
            streamList.add(Files.newInputStream(Paths.get(wordPath)));
        }
        mergeDocStream(streamList, new FileOutputStream(outFilePath));
    }

    /**
     * 合并word文件
     *
     * @param streamList 文件流列表
     * @param out        输出流
     * @throws Docx4JException 异常
     * @throws IOException     异常
     */
    private static void mergeDocStream(List<InputStream> streamList, FileOutputStream out) throws Docx4JException, IOException {
        // 创建一个空的临时Word文档作为基础
        WordprocessingMLPackage target = WordprocessingMLPackage.createPackage();
        int chunkId = 0;
        // 将streamList中的每个文档插入到target中
        for (InputStream inputStream : streamList) {
            if (inputStream != null) {
                insertDoc(target.getMainDocumentPart(), inputStream, chunkId++);
            }
        }
        target.save(out);
        out.close();

    }

    /**
     * 插入文档
     *
     * @param mainDocumentPart mainDocumentPart
     * @param inputStream      inputStream
     * @param chunkId          chunkId
     * @throws InvalidFormatException 异常
     */
    private static void insertDoc(MainDocumentPart mainDocumentPart, InputStream inputStream, int chunkId) throws InvalidFormatException {
        PartName partName = new PartName("/part" + chunkId + ".docx");
        AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart(partName);
        afiPart.setBinaryData(inputStream);
        Relationship relationship = mainDocumentPart.addTargetPart(afiPart);
        CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk();
        chunk.setId(relationship.getId());
        mainDocumentPart.addObject(chunk);
    }

}

4.导出接口

@ApiOperation(value = "慈善项目基本信息、实施计划、成果公开一本账导出")
@GetMapping("export")
public void export(HttpServletResponse response, String id) throws Exception {
    if (StrUtil.isBlank(id)) {
        throw new FlowException("请选择要导出的慈善项目!");
    }
    List<String> list = Arrays.asList(id.split(","));
    String filePath = collaborateProjectService.export(response, list);
    File file = new File(filePath);

    // 检查文件是否存在
    if (!file.exists()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found");
        return;
    }

    // 设置响应头
    String fileName = file.getName(); // "慈善项目_20250110135423.zip"
    String encodedFileName = URLEncoder.encode(fileName, "UTF-8"); // 现代浏览器应该能够处理这个编码
    response.setContentType("application/zip");
    response.setHeader("Content-Disposition", "attachment; filename*=utf-8''" + encodedFileName);
    System.out.println("file.getName() =================== " + file.getName());
    response.setContentLengthLong(file.length());

    // 获取响应输出流
    try (OutputStream outputStream = response.getOutputStream();
         FileInputStream fileInputStream = new FileInputStream(file)) {

        // 将文件内容写入响应输出流
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = fileInputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        outputStream.flush();
    }
}
## service业务层
String export(HttpServletResponse response, List<String> list) throws Exception;

## serviceImpl业务层
@Override
public String export(HttpServletResponse response, List<String> list) throws Exception {
    // 要导出的慈善项目id集合
    // List<String> list = new ArrayList<>(Arrays.asList("1876092587459440641", "1876105476132007938"));
    if (CollUtil.isEmpty(list)) {
        throw new FlowException("请选择您要导出的慈善项目!");
    }
    // 程序入口,主要作用是为多个项目分别根据模版组装数据后合并成一个word
    // 1. 准备数据(模拟从数据库中查询,key为项目名称,value为项目数据)
    Map<String, ProjectData> dataMap = queryData(list);
    // 2. 为每个项目生成一个以项目名为名称的文件夹,文件夹内包括word文档、附件
    // 2.1 创建一个临时文件夹
    String tempDirPath = FileUtil.mkParentDirs("temp/" + UUID.randomUUID()).getAbsolutePath();
    // 2.2 在这个临时文件夹下面,根据数据生成多个项目文件夹及word文档、附件
    GenerateOnWordTemplateKit.generateAllWordDocument(dataMap, tempDirPath);
    // 3. 打包整个临时文件夹为zip压缩包
    File projectZipFile = GenerateOnWordTemplateKit.compressIntoAZipFile(tempDirPath);
    System.out.println("压缩包路径保存:" + projectZipFile.getAbsolutePath());

    // 5. 删除所有临时文件
    GenerateOnWordTemplateKit.deleteAllTempFiles(tempDirPath);

    return projectZipFile.getAbsolutePath();
}
/**
 * 模拟从数据库中查询数据
 * List<String> list 慈善项目主键id
 * @return 返回一个模拟数据
 */
public Map<String, ProjectData> queryData(List<String> list) throws IOException {
    Map<String, ProjectData> dataMap = new HashMap<>();
    for (String s : list) {
        // 创建模拟项目数据
        ProjectData project = new ProjectData();
        CollaborateProjectVO projectVO = getCollaborateProjectVO(s);
        if (ValidateUtil.isEmpty(projectVO)) {
            continue;
        }
        project.setProjectName(projectVO.getProjectName());
        project.setBsoName(projectVO.getProjectExecutingPartyList().get(0).getBsoName());
        project.setSynopsis(projectVO.getProjectExecutingPartyList().get(0).getSynopsis());
        project.setPersonName(projectVO.getProjectExecutingPartyList().get(0).getPersonName());
        project.setPersonPhone(projectVO.getProjectExecutingPartyList().get(0).getPersonPhone());
        project.setProjectStartTime(DateUtil.format(projectVO.getProjectStartTime(), "yyyy-MM-dd"));
        project.setProjectEndTime(DateUtil.format(projectVO.getProjectEndTime(), "yyyy-MM-dd"));
        // 区划处理
        List<String> areaNameList = areaFieldMapper.queryAreaNameByAreaCodeList(projectVO.getAdmsAreaList());
        if (CollUtil.isNotEmpty(areaNameList)) {
            project.setAdmsAreaListName(String.join(", ", areaNameList));
        } else {
            project.setAdmsAreaListName(null);
        }
        project.setBudget(String.valueOf(projectVO.getBudget()));
        // 项目性质
        List<String> projectQualities = cpServeObjectMapper.queryServeNameByTypeAndValue(projectVO.getProjectQuality(), "PROJECTNATURE");
        if (CollUtil.isNotEmpty(projectQualities)) {
            project.setProjectQuality(projectQualities.get(0));
        } else {
            project.setProjectQuality(null);
        }

        // 项目状态
        List<String> projectStatus = cpServeObjectMapper.queryServeNameByTypeAndValue(projectVO.getProjectQuality(), "PROJECTSTATUS");
        if (CollUtil.isNotEmpty(projectStatus)) {
            project.setProjectStatus(projectStatus.get(0));
        } else {
            project.setProjectStatus(null);
        }

        // 服务领域
        List<String> demandTypes = cpServeObjectMapper.queryServeNameByTypeAndValue(projectVO.getDemandType(), "SERVICEAREA");
        if (CollUtil.isNotEmpty(demandTypes)) {
            project.setServiceArea(demandTypes.get(0));
        } else {
            project.setServiceArea(null);
        }
        project.setProjectBackground(projectVO.getProjectBackground());
        project.setProjectedOutputs(projectVO.getProjectedOutputs());
        // 服务对象处理
        List<String> types = cpServeObjectMapper.queryServeNameByType(projectVO.getCpServeObjectPoList(), "SERVICEOBJECT");
        if (CollUtil.isNotEmpty(types)) {
            project.setCpServeObjectPoListName(String.join(", ", types));
        } else {
            project.setCpServeObjectPoListName(null);
        }
        project.setServeNum(String.valueOf(projectVO.getServeNum()));
        project.setFunderName(projectVO.getFunderName());
        // 资金来源处理
        List<String> sourceFunds = cpServeObjectMapper.queryServeNameByTypeAndValue(projectVO.getFundSource(), "SOURCEFUNDS");
        if (CollUtil.isNotEmpty(sourceFunds)) {
            project.setFundSource(String.join(", ", sourceFunds));
        } else {
            project.setFundSource(null);
        }
        // 项目分类处理
        List<String> projectClasses = cpServeObjectMapper.queryServeNameByType(projectVO.getCpTypePoList(), "PROJECTCLASS");
        if (CollUtil.isNotEmpty(projectClasses)) {
            project.setCpTypePoListName(String.join(", ", projectClasses));
        } else {
            project.setCpTypePoListName(null);
        }
        project.setProjectInnovation(projectVO.getProjectInnovation());
        project.setRiskMeasure(projectVO.getRiskMeasure());
        List<String> filePathList01 = new ArrayList<>();
        List<String> filePathList02 = new ArrayList<>();
        // 下载附件到本地
        if (CollUtil.isNotEmpty(projectVO.getOssIdList())) {
            for (AttachmentVO attachmentVO : projectVO.getOssIdList()) {
                GenerateOnWordTemplateKit.downloadImage(attachmentVO.getUrl(), GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName());
                filePathList01.add(GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName());
            }
        }
        // 下载评估报告到本地
        if (CollUtil.isNotEmpty(projectVO.getAssReportList())) {
            for (AttachmentVO attachmentVO : projectVO.getAssReportList()) {
                GenerateOnWordTemplateKit.downloadImage(attachmentVO.getUrl(), GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName());
                filePathList01.add(GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName());
            }
        }
        project.setFilePathList01(filePathList01); // 附件
        project.setFilePathList02(filePathList02); // 评估报告

        // 实施计划
        List<ImplementPlanPO> implementPlanPOList = planMapper.selectList(new LambdaQueryWrapper<ImplementPlanPO>()
                .eq(ImplementPlanPO::getEffective, "1")
                .eq(ImplementPlanPO::getProjectId, s));
        if (CollUtil.isNotEmpty(implementPlanPOList)) {
            List<Plan> plans = new ArrayList<>();
            for (int j = 0; j < implementPlanPOList.size(); j++) {
                Plan plan = new Plan();
                plan.setPlanName("实施计划-" + (j + 1));
                plan.setStartTime(DateUtil.format(implementPlanPOList.get(j).getStartTime(), "yyyy-MM-dd"));
                plan.setEndTime(DateUtil.format(implementPlanPOList.get(j).getEndTime(), "yyyy-MM-dd"));
                plan.setNumPeople(String.valueOf(implementPlanPOList.get(j).getNumPeople()));
                plan.setActivityForm(implementPlanPOList.get(j).getActivityForm());
                plan.setLocation(implementPlanPOList.get(j).getLocation());
                plan.setActiveFrequency(implementPlanPOList.get(j).getActiveFrequency());
                plan.setActiveGoal(implementPlanPOList.get(j).getActiveGoal());
                plan.setNotes(implementPlanPOList.get(j).getNotes());
                plans.add(plan);
            }
            project.setPlans(plans);
        }

        // 成果公开
        List<FeedBackPO> feedBackPOList = feedBackMapper.selectList(new LambdaQueryWrapper<FeedBackPO>()
                .eq(FeedBackPO::getEffective, "1")
                .eq(FeedBackPO::getProjectId, s));

        if (CollUtil.isNotEmpty(feedBackPOList)) {
            List<Achievement> achievements = new ArrayList<>();
            for (FeedBackPO feedBackPO : feedBackPOList) {
                Achievement achievement = new Achievement();
                FeedBackVO feedBackVO = feedBackService.detail(feedBackPO.getId());
                if (ValidateUtil.isEmpty(feedBackVO)) {
                    continue;
                }
                achievement.setProjectName(feedBackVO.getProjectName());
                achievement.setTitle(feedBackVO.getTitle());
                // 活动类型处理
                List<String> activityTypeList = cpServeObjectMapper.queryServeNameByTypeAndValue(feedBackVO.getActivityType(), "ACTIVITYTYPE");
                if (CollUtil.isNotEmpty(activityTypeList)) {
                    achievement.setActivityType(String.join(", ", activityTypeList));
                } else {
                    achievement.setActivityType(null);
                }
                achievement.setActivityTime(feedBackVO.getTime());
                achievement.setAddress(feedBackVO.getAddress());
                // 区划处理
                List<String> areaNameByAreaCodeList = areaFieldMapper.queryAreaNameByAreaCodeList(feedBackVO.getAdmsAreaList());
                if (CollUtil.isNotEmpty(areaNameByAreaCodeList)) {
                    achievement.setAdmsAreaListName(String.join(", ", areaNameByAreaCodeList));
                } else {
                    achievement.setAdmsAreaListName(null);
                }
                achievement.setPersonNumber(feedBackVO.getPersonNumber());
                achievement.setDonationAmount(feedBackVO.getDonationAmount());
                achievement.setDuration(feedBackVO.getDuration());
                achievement.setSatisfaction(String.valueOf(feedBackVO.getSatisfaction()));
                achievement.setImplementContent(feedBackVO.getImplementContent());
                List<TaUserPO> taUserPOS = taUserMapper.selectList(new LambdaQueryWrapper<TaUserPO>()
                        .eq(TaUserPO::getDestroy, "0")
                        .eq(TaUserPO::getEffective, "1")
                        .eq(StrUtil.isNotBlank(feedBackVO.getUpdateUser()), TaUserPO::getLoginId, feedBackVO.getUpdateUser()));
                if (CollUtil.isNotEmpty(taUserPOS)) {
                    achievement.setUpdateUser(taUserPOS.get(0).getName());
                } else {
                    achievement.setUpdateUser(feedBackVO.getUpdateUser());
                }
                achievement.setUpdateTime(DateUtil.format(feedBackVO.getUpdateTime(), "yyyy-MM-dd hh:mm:ss"));

                List<String> attachmentPOList = new ArrayList<>();
                List<String> attachmentPOList2 = new ArrayList<>();
                // 附件地址,这里可以是文件key,配合download方法能下载到本地即可

                // 佐证材料
                if (CollUtil.isNotEmpty(feedBackVO.getAttachmentPOList())) {
                    for (AttachmentVO attachmentVO : feedBackVO.getAttachmentPOList()) {
                        GenerateOnWordTemplateKit.downloadImage(attachmentVO.getUrl(), GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName());
                        attachmentPOList.add(GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName());
                    }
                }
                // 其他材料
                if (CollUtil.isNotEmpty(feedBackVO.getAttachmentPOList2())) {
                    for (AttachmentVO attachmentVO : feedBackVO.getAttachmentPOList2()) {
                        GenerateOnWordTemplateKit.downloadImage(attachmentVO.getUrl(), GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName());
                        attachmentPOList2.add(GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName());
                    }
                }
                achievement.setFilePathList1(attachmentPOList);
                achievement.setFilePathList2(attachmentPOList2);
                achievements.add(achievement);
            }
            project.setAchievements(achievements);
        }

        dataMap.put(project.getProjectName(), project);
    }
    return dataMap;
}

5.设计模版文件

6.前端调用方式

<ta-button  class="mt-10" @click="openWinExcel" type="primary" icon="to-top">

openWinExcel() {

      // this.dcVisible = true

      const param = {}

      const idList = []

      const selectRecords = this.$refs.sTable.$refs.mTable.getCheckboxRecords()

      selectRecords.map(item => {

        idList.push(item.id)

      })

      param.idList = idList

      if (param.idList.length == 0) {

        this.$message.warning('请选择您要导出的慈善项目!')

        return

      }

      const id = param.idList.join(',');

      window.open(faceConfig.basePath + '/collaborateProject/export?id=' + id, '_blank');

      this.reTable(); // 刷新表格

    },

实现结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只鸟儿

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值