报表技术2(百万数据导入导出,POI操作word)

POI模板导出,操作word

  • 导出用户详情数据(图片,公式处理)
    • 使用模板导出用户详细信息
    • 使用模板引擎
      • 1.编写模板引擎
      • 2.使用模板引擎
  • 百万数据导出
    • 代码实现:
  • 百万数据导入
    • 步骤分析:
    • 1.自定义处理器
    • 2.自定义解析
    • 3.测试
  • CSV文件
    • 导出csv文件:
    • 读取csv文件
  • POI导出Word(.docx)
    • API介绍
    • 思路分析
    • 代码实现

Java-报表技术1-POI:https://blog.csdn.net/weixin_43994244/article/details/126227702

导出用户详情数据(图片,公式处理)

模板样式
报表技术2(百万数据导入导出,POI操作word)_第1张图片

使用模板导出用户详细信息

    //日期格式化
    private SimpleDateFormat simpleDateFormat = new  SimpleDateFormat("yyyy-MM-dd");

  /**
     * 导出用户详细信息--使用模板
     * @param id
     * @param response
     * @throws Exception
     */
    public void downLoadUserInfoByTemplate(Long id, HttpServletResponse response) throws Exception {
      
        //1.读取模板
        //获取项目的根目录,创建目录---获取绝对路径
        File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());
        File templateFile = new File(rootPath.getAbsolutePath(), "/excel_template/userInfo.xlsx");
        //创建有模板的工作簿
        Workbook workbook = new XSSFWorkbook(templateFile);
        Sheet sheet = workbook.getSheetAt(0);
      
        //2.获取用户数据
        User user = userMapper.selectByPrimaryKey(id);
     
        //3.数据放入模板---存入哪一行哪个单元格
        //用户名 第2行第2列
        sheet.getRow(1).getCell(1).setCellValue(user.getUserName());
        //手机号 第3行第2列
        sheet.getRow(2).getCell(1).setCellValue(user.getPhone());
        //生日 第4行第2列
        sheet.getRow(3).getCell(1).setCellValue(simpleDateFormat.format(user.getBirthday()));
        //工资 第5行第2列
        sheet.getRow(4).getCell(1).setCellValue(user.getSalary());
        //入职日期 第6行第2列
        sheet.getRow(5).getCell(1).setCellValue(simpleDateFormat.format(user.getHireDate()));
        //省份 第7行第2列
        sheet.getRow(6).getCell(1).setCellValue(user.getProvince());
        //城市 第7行第4列
        sheet.getRow(6).getCell(3).setCellValue(user.getCity());
        //现住址 第8行第1列---合并单元格
        sheet.getRow(7).getCell(1).setCellValue(user.getAddress());
     
        //司龄 第6行第4列---导出公式(入职时间到现在)
        // --填入excel的公式=CONCATENATE(DATEDIF(B6,TODAY(),"Y"),"年",DATEDIF(B6,TODAY(),"YM"),"个月")
        sheet.getRow(5).getCell(3).setCellFormula("CONCATENATE(DATEDIF(B6,TODAY(),\"Y\"),\"年\",DATEDIF(B6,TODAY(),\"YM\"),\"个月\")");
         
         //图片处理开始
        //照片--第二行到第五行,第三列到第四列--合并单元格
         String userPhoto = user.getPhoto();
         
        //先创建一个字节输出流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
     
       //优化,获取拓展名,根据不同拓展名
        String extName = userPhoto.substring(userPhoto.lastIndexOf(".") + 1).toUpperCase();
    
        //读取图片,放入一个带有缓冲区的图片类中,主要作用是将一副图片加载到内存中
        BufferedImage bufferedImage = ImageIO.read(new File(rootPath+user.getPhoto()));
     
        //把图片写入到字节输出流中
        ImageIO.write(bufferedImage,extName,outputStream);
     
        //Patriarch图片写入
        Drawing patriarch = sheet.createDrawingPatriarch();
     
        //ClientAnchor指定图片位置--后四起始列,起始行,结束列,结束行,前四个参数,左上角向x,y偏离多少,右下角向x,y偏离多少(偏移单位-1cm=36w)  不占满表格
        ClientAnchor clientAnchor = new XSSFClientAnchor(0, 0, 0, 0, 2, 1, 4, 5);
     
        //优化
        int format = 0;
        switch (extName){
            case "JPG":{
                format = XSSFWorkbook.PICTURE_TYPE_JPEG;
            } case "JPEG":{
                format = XSSFWorkbook.PICTURE_TYPE_JPEG;
            } case "PNG":{
                format = XSSFWorkbook.PICTURE_TYPE_PNG;
            }
        }
       
        //开始把图片写入到sheet指定的位置
        patriarch.createPicture(clientAnchor,workbook.addPicture(outputStream.toByteArray(),format));
        
        //图片处理结束

        //4.下载
        //一个流两个头
        String fileName = "员工("+user.getUserName()+")详细数据.xlsx";
        response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));
        response.setContentType("application/vnd.ms-excel");
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        //执行
        workbook.write(response.getOutputStream());
    }

使用模板引擎

前提:导出代码时,必须要提前知道要导出数据在哪一行哪一个单元格,但是模板一旦发生调整,java代码也要修改,所以可以自定义引擎,有了引擎即使模板修改了java代码也不用修改。
思路:在制作模板时,在需要插入数据的位置做上标记,在导出时,对象的属性和标记做对应,如果对应匹配一样,就把值赋值到相应的位置。

模板:
报表技术2(百万数据导入导出,POI操作word)_第2张图片
工具类: bean和map转换
可以借用hutool的BeanUtil里的方法:

此处是用jdk的API编写:
public class EntityUtils {

  private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

  /**
   * 实体类转Map
   * @param object
   * @return
   */
  public static Map<String, Object> entityToMap(Object object) {
    Map<String, Object> map = new HashMap();
    for (Field field : object.getClass().getDeclaredFields()){
      try {
        boolean flag = field.isAccessible();
        field.setAccessible(true);
        Object o = field.get(object);
        if(o instanceof Date){
          o = sdf.format(o);
        }
        map.put(field.getName(), o);
        field.setAccessible(flag);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    return map;
  }
  
  /**
   * Map转实体类
   * @param map 需要初始化的数据,key字段必须与实体类的成员名字一样,否则赋值为空
   * @param entity 需要转化成的实体类
   * @return
   */
  public static <T> T mapToEntity(Map<String, Object> map, Class<T> entity) {
    T t = null;
    try {
      t = entity.newInstance();
      for(Field field : entity.getDeclaredFields()) {
        if (map.containsKey(field.getName())) {
          boolean flag = field.isAccessible();
          field.setAccessible(true);
          Object object = map.get(field.getName());
          if (object!= null && field.getType().isAssignableFrom(object.getClass())) {
             field.set(t, object);
          }
          field.setAccessible(flag);
        }
      }
      return t;
    } catch (InstantiationException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return t;
  }
}

1.编写模板引擎

public class ExcelExportEngine {
    private static SimpleDateFormat  sdf = new SimpleDateFormat("yyyy-MM-dd");


    public static Workbook writeToExcel(Object object,Workbook workbook,String photoPath) throws Exception {
      
        //把bean转成map
        Map<String, Object> map = EntityUtils.entityToMap(object);
        Sheet sheet = workbook.getSheetAt(0);
        Row row = null;
        Cell cell = null;
        //循环100行,每一行循环100个单元格---定位到具体的cell
        for (int i = 0; i < 10; i++) {
            row = sheet.getRow(i);
            if(row == null){
                break;}
            else {
                for (int j = 0; j < 10; j++) {
                    cell = row.getCell(j);
                    if(cell != null){
                    //比较单元格中的值,是否和map中的key一致,如果一致向单元格中放入map这个key对应的值
                        writeToCell(cell,map);
                    }
                }
            }
        }

        //处理图片
        if(StringUtils.isNotBlank(photoPath)){
            //File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
     
            ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
     
            //BufferedImage是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中
            BufferedImage bufferImg = ImageIO
                    .read(new File(photoPath));
            ImageIO.write(bufferImg, "jpg", byteArrayOut);
            Drawing patriarch = sheet.createDrawingPatriarch();
     
            //获取模板图片位置
            Sheet sheet2 = workbook.getSheetAt(1);
             row = sheet2.getRow(0);
            int col1 = ((Double) row.getCell(0).getNumericCellValue()).intValue();
            int row1 = ((Double) row.getCell(1).getNumericCellValue()).intValue();
            int col2 = ((Double) row.getCell(2).getNumericCellValue()).intValue();
            int row2 = ((Double) row.getCell(3).getNumericCellValue()).intValue();
            //锚点,固定点
            ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0,  col1, row1, col2, row2);
            patriarch.createPicture(anchor, workbook.addPicture(byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_JPEG));
            //删除第二个sheet
            workbook.removeSheetAt(1);
        }
        return workbook;
    }

    //比较单元格中的值,是否和map中的key一致,如果一致向单元格中放入map这个key对应的值
    private static void writeToCell(Cell cell, Map<String, Object> map) {
        CellType cellType = cell.getCellType();
        //公式的话不处理
        switch(cellType){
            case FORMULA:{
                break;
            } default:{
                String cellValue = cell.getStringCellValue();
                if(StringUtils.isNotBlank(cellValue)){
                   //System.out.println("获取到的cellValue"+cellValue);
                    for (String key : map.keySet()){
                        if(key.equals(cellValue)){
                            cell.setCellValue(map.get(key).toString());
                      //System.out.println("获取到的value:"+map.get(key).toString());
                        }
                    }
                }
            }
        }
    }
}

2.使用模板引擎

    /**
     * 导出用户详细信息--使用模板引擎
     * @param id
     * @param response
     * @throws Exception
     */
    public void downLoadUserInfoByTemplate2(Long id, HttpServletResponse response) throws Exception {
        
        //1.读取模板
        //获取项目的根目录,创建目录---获取绝对路径
        File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());
        File templateFile = new File(rootPath.getAbsolutePath(), "/excel_template/userInfo2.xlsx");
        //创建有模板的工作簿
        Workbook workbook = new XSSFWorkbook(templateFile);
        Sheet sheet = workbook.getSheetAt(0);
      
        //2.获取用户数据
        User user = userMapper.selectByPrimaryKey(id);
        System.out.println("获取到的用户信息"+user.toString());
      
        //3.通过自定义引擎放入数据--图片的绝对路径
        workbook = ExcelExportEngine.writeToExcel(user, workbook,rootPath.getPath()+user.getPhoto());
        patriarch.createPicture(clientAnchor,workbook.addPicture(outputStream.toByteArray(),format));
        //图片处理结束
        
        //4.下载
        //一个流两个头
        String fileName = "员工("+user.getUserName()+")详细数据.xlsx";
        response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));
        //xls
        //response.setContentType("application/vnd.ms-excel");
        //xlsx
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        //执行
        workbook.write(response.getOutputStream());
    }

百万数据导出

用户模式: 用户模式有许多封装好的方法操作简单,但创建太多的对象,非常耗内存(之前使用的方法)
事件模式: 基于SAX方式解析XML,SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文档,一边扫描,一边解析。
SXSSF对象:是用来生成海量excel数据文件,主要原理是借助临时存储空间生成excel
弊端
1、不能使用模板
2、不能使用太多的样式
实现方式:
1.dom4j:一次性加载xml文件再解析
2.SAX:逐行加载,逐行解析
3.SXSSWorkBook:一旦内存中的对象的个数达到指定值时,就将内存中的这些对象的内容写入到磁盘中,就可以将这些对象从内存中销毁,直至excel导出完成。

代码实现:

   public void downLoadMillion(HttpServletResponse response) throws Exception {
        //使用sax方式解析
        Workbook workbook = new SXSSFWorkbook();
        //导出500w条数据,一个sheet只能放100w行

        //记录了处理了多少条数据
        int number = 0;
        //分页查询
        int page = 1;
        //记录row的行索引
        int rowIndex = 1;
        //创建行
        Row row = null;
        Sheet sheet = null;
        while (true) {
            List<User> userList = this.findPage(page, 1000000);
            if (CollectionUtils.isEmpty(userList)) {
                break;//数据为空不再查询
            }

            if (number % 1000000 == 0) {
                //表示应该创建新的标题
                 sheet = workbook.createSheet("第" + (number/1000000)+ "个sheet表");
                //重置rowIndex
                rowIndex = 1;

                //设置列宽
                sheet.setColumnWidth(0,8*256);
                sheet.setColumnWidth(1,12*256);
                sheet.setColumnWidth(2,15*256);
                sheet.setColumnWidth(3,15*256);
                sheet.setColumnWidth(4,30*256);

                //设置小标题
                String[] title = new String[]{"编号", "姓名", "手机号", "入职日期", "现住址"};
                //创建标题行
                Row titleRow = sheet.createRow(0);
                for (int i = 0; i < 5; i++) {
                    titleRow.createCell(i).setCellValue(title[i]);
                }
            }
            //向excel中添加内容

            for (User user : userList) {
                row = sheet.createRow(rowIndex);
                row.createCell(0).setCellValue(user.getId());
                row.createCell(1).setCellValue(user.getUserName());
                row.createCell(2).setCellValue(user.getPhone());
                row.createCell(3).setCellValue(simpleDateFormat.format(user.getHireDate()));
                row.createCell(4).setCellValue(user.getAddress());

                rowIndex++;
                number++;
            }
            page++;//下一页

        }

        //4.下载
        //一个流两个头
        String fileName = "百万数据导出.xlsx";
        response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        //执行
        workbook.write(response.getOutputStream());
    }

百万数据导入

用户模式: 加载并读取Excel时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。
事件模式: 它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。

步骤分析:

1.设置POI的事件模式

  • 根据Excel获取文件流
  • 根据文件流创建OPCPackage ,用来组合读取到的xml 组合出来的数据占用的空间更小
  • 创建XSSFReader对象

2.Sax解析

  • 自定义Sheet处理器
  • 创建Sax的XmlReader对象
  • 设置Sheet的事件处理器
  • 逐行读取

1.自定义处理器

package com.itheima.test;

import com.itheima.pojo.User;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;

public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {

//    编号 用户名  手机号  入职日期 现住址
    private User user=null;
    @Override
    public void startRow(int rowIndex) { //每一行的开始   rowIndex代表的是每一个sheet的行索引
        if(rowIndex==0){
            user = null;
        }else{
            user = new User();
        }
    }
    @Override  //处理每一行的所有单元格
    public void cell(String cellName, String cellValue, XSSFComment comment) {

        if(user!=null){
            String letter = cellName.substring(0, 1);  //每个单元名称的首字母 A  B  C
            switch (letter){
                case "A":{
                    user.setId(Long.parseLong(cellValue));
                    break;
                }
                case "B":{
                    user.setUserName(cellValue);
                    break;
                }
            }
        }
    }
    @Override
    public void endRow(int rowIndex) { //每一行的结束
        if(rowIndex!=0){
            System.out.println(user);
        }

    }
}

2.自定义解析

package com.itheima.test;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.InputStream;

/**
 * 自定义Excel解析器
 */
public class ExcelParser {

    public void parse (String path) throws Exception {
        //1.根据Excel获取OPCPackage对象
        OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);
        try {
            //2.创建XSSFReader对象
            XSSFReader reader = new XSSFReader(pkg);
            //3.获取SharedStringsTable对象
            SharedStringsTable sst = reader.getSharedStringsTable();
            //4.获取StylesTable对象
            StylesTable styles = reader.getStylesTable();
            XMLReader parser = XMLReaderFactory.createXMLReader();
            // 处理公共属性:Sheet名,Sheet合并单元格
            parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst, new SheetHandler(), false));
            XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) reader.getSheetsData();
            while (sheets.hasNext()) {
                InputStream sheetstream = sheets.next();
                InputSource sheetSource = new InputSource(sheetstream);
                try {
                    parser.parse(sheetSource);
                } finally {
                    sheetstream.close();
                }
            }
        } finally {
            pkg.close();
        }
    }
}

3.测试

用户模式下读取测试Excel文件直接内存溢出,测试Excel文件映射到内存中还是占用了不少内存;事件模式下可以流畅的运行。

public class POIDemo5 {
    public static void main(String[] args) throws Exception{
        new ExcelParser().parse("C:\\Users\\syl\\Desktop\\百万用户数据的导出.xlsx");
    }
}

CSV文件

CSV文件:Comma-Separated Values,中文叫逗号分隔值或者字符分割值,其文件以纯文本的形式存储表格数据。该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分割。每条记录由字段组成,字段间的分隔符是其他字符或者字符串。所有的记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式。

用文本文件、excel或者类似与文本文件的编辑器都可以打开CSV文件。

使用opencsv类库来导出csv文
依赖:

<dependency>
    <groupId>com.opencsvgroupId>
    <artifactId>opencsvartifactId>
    <version>4.5version>
dependency>

opencsv常用API:

写入到csv文件会用到CSVWriter对象,创建此对象常见API如下:
CSVWriter(Writer writer);
CSVWriter(Writer writer, char separator);
CSVWriter(Writer writer, char separator, char quotechar);

构造器涉及到的三个参数:

  1. reader:读取文件的流对象,常有的是BufferedReader,InputStreamReader。
  2. separator:用于定义前面提到的分割符,默认为逗号CSVWriter.DEFAULT_SEPARATOR用于分割各列。
  3. quotechar:用于定义各个列的引号,有时候csv文件中会用引号或者其它符号将一个列引起来,例如一行可能是:“1”,“2”,“3”,如果想读出的字符不包含引号,就可以把参数设为:"CSVWriter.NO_QUOTE_CHARACTER "

使用CSVWriter对象写入数据常用的方法如下:
writerAll(List allLines);
writerNext(String… nextLine);

读取csv文件会用到CSVReader对象,创建此对象常见API如下:
CSVReader(Reader reader)
CSVReader(Reader reader, char separator)
CSVReader(Reader reader, char separator, char quotechar)

导出csv文件:

   /**
     * 导出百万数据到CSV文件
     * @param response
     * @throws Exception
     */
    public void downLoadCSV(HttpServletResponse response) throws Exception {
        ServletOutputStream outputStream = response.getOutputStream();

        //导出
        String fileName = "csv百万数据导出.csv";
        response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));
        response.setContentType("text/csv");
        
        //写文件
        CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(outputStream,"UTF-8"));
        //写第一行
        //设置小标题
        String[] title = new String[]{"编号", "姓名", "手机号", "入职日期", "现住址"};
        csvWriter.writeNext(title);
        
        int page = 1;
        while (true) {
            List<User> userList = this.findPage(1, 2000000);
         if(CollectionUtils.isEmpty(userList)){
             break;
         }for(User user : userList){
             csvWriter.writeNext(new String[]{user.getId().toString(),user.getUserName(),user.getPhone(),simpleDateFormat.format(user.getHireDate()),user.getAddress()});
            }
         page++;
         csvWriter.flush();
        }
        csvWriter.close();
    }

读取csv文件

import com.opencsv.CSVReader;

import java.io.FileReader;
import java.text.SimpleDateFormat;
import java.time.Year;
import java.util.List;

//读取百万级数据的csv文件
public class CsvDemo {`在这里插入代码片`

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) throws Exception {
        CSVReader csvReader = new CSVReader(new FileReader("d:\\百万用户数据的导出.csv"));
        String[] titles = csvReader.readNext(); //读取到第一行 是小标题
//        "编号","姓名","手机号","入职日期","现住址"
        User user = null;
        while (true){
            user = new User();
            String[] content = csvReader.readNext();
            if(content==null){
                break;
            }
            user.setId(Long.parseLong(content[0]));
            user.setUserName(content[1]);
            user.setPhone(content[2]);
            user.setHireDate(simpleDateFormat.parse(content[3]));
            user.setAddress(content[4]);
            System.out.println(user);
        }
    }
}

POI导出Word(.docx)

API介绍

XWPFDocument代表一个docx文档,其可以用来读docx文档,也可以用来写docx文档

一个文档包含多个段落,一个段落包含多个Runs文本,一个Runs包含多个Run,Run是文档的最小单元

获取所有段落:List paragraphs = word.getParagraphs();

获取一个段落中的所有片段Runs:List xwpfRuns = xwpfParagraph.getRuns();

获取一个Runs中的一个Run:XWPFRun run = xwpfRuns.get(index);

2、poi操作word中的表格

一个文档包含多个表格,一个表格包含多行,一行包含多列单元格

获取所有表格:List xwpfTables = doc.getTables();

获取一个表格中的所有行:List xwpfTableRows = xwpfTable.getRows();

获取一行中的所有列:List xwpfTableCells = xwpfTableRow.getTableCells();

获取一格里的内容:List paragraphs = xwpfTableCell.getParagraphs();

之后和正文段落一样

思路分析

制作一个word模板,把动态的内容先写特殊字符然后替换,表格自己创建然后向表格中放内容
报表技术2(百万数据导入导出,POI操作word)_第3张图片

代码实现

1.准备方法:一个是想指定的单元格中放入图片,另一个是 复制word中表格的行

    /**
     * 向单元格写入图片
     */
    private void setCellImage(XWPFTableCell cell, File imageFile) {
        XWPFRun run = cell.getParagraphs().get(0).createRun();
        //流会自动关闭
        try(FileInputStream inputStream = new FileInputStream(imageFile)) {
            //输入流,图片类型,文件名,高度,宽度(图片扩大倍数)
            run.addPicture(inputStream,XWPFDocument.PICTURE_TYPE_JPEG,imageFile.getName(), Units.toEMU(100), Units.toEMU(50));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 用户深复制行
     */
    private void copyRow(XWPFTable table, XWPFTableRow sourceRow, int rowIndex) {
        XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);
        //设置行属性
        targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
        //获取源行的单元格--设置单元格属性
        List<XWPFTableCell> cells = sourceRow.getTableCells();
        //
        if(CollectionUtils.isEmpty(cells)){
            return;
        }
          XWPFTableCell targetCell = null;
            for (XWPFTableCell cell : cells) {
                targetCell = targetRow.addNewTableCell();
                //附上单元格的样式
                //单元格的样式
                targetCell.getCTTc().setTcPr(cell.getCTTc().getTcPr());
                //单元个中段落属性
                targetCell.getParagraphs().get(0).getCTP().setPPr(cell.getParagraphs().get(0).getCTP().getPPr());
            }
        }

2.下载用户合同

    /**
     * 下载用户合同
     * @param id
     */
    public void downloadContract(Long id,HttpServletResponse response) throws Exception {
    
        //1.读取到模板
        //获取项目的根目录,创建目录---获取绝对路径
        File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());
        File templateFile = new File(rootPath.getAbsolutePath(), "word_template/contract_template.docx");
        XWPFDocument document = new XWPFDocument(new FileInputStream(templateFile));
        
        //2.查询当前用户--->map方便操作
        User user = this.findById(id);
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("userName",user.getUserName());
        hashMap.put("hireDate",simpleDateFormat.format(user.getHireDate()));
        hashMap.put("address",user.getAddress());
        
        //3.1.替换数据--处理正文
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            List<XWPFRun> runs = paragraph.getRuns();
            for (XWPFRun run : runs) {
                String text = run.getText(0);
                //循环遍历是否包含map的key
                for (String key : hashMap.keySet()) {
                    if(text.contains(key)){
                        //如果包含,替换掉
                        run.setText(text.replaceAll(key,hashMap.get(key)),0);
                    }
                }
            }
        }
        
        //3.2.表格数据
        List<Resource> resources = user.getResourceList();
        //获取表格
        XWPFTable table = document.getTables().get(0);
        //获取模板的第一行
        XWPFTableRow row = table.getRow(0);
      
        //存放东西,向表格中添加行
        int rowIndex = 1;
        for (Resource resource : resources) {
            //添加行
            //table.addRow(row);
            copyRow(table,row,rowIndex);
            XWPFTableRow tableRow = table.getRow(rowIndex);
            tableRow.getCell(0).setText(resource.getName());
            tableRow.getCell(1).setText(resource.getPrice().toString());
            tableRow.getCell(2).setText(resource.getNeedReturn()?"需要":"不需要");
       
            //照片处理
            File imageFile = new File(rootPath, "/static"+resource.getPhoto());
            setCellImage(tableRow.getCell(3),imageFile);
            
            rowIndex++;
        }

        //4.导出word
        String fileName = "员工"+user.getUserName()+"合同.docx";
        response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        document.write(response.getOutputStream());
    }

你可能感兴趣的:(文件,java)