springboot实现文件的输入输出

这里主要是实现excel,CSV文件的输入输出及PDF的输出。
1,首先是excel,使用poi实现导入导出

POI详细介绍参考:https://www.cnblogs.com/huajiezh/p/5467821.html
常识:
xlsx和xls都是excel文件名扩展名,xls是早期excel使用的,xlsx为后续excel版本才开始使用。主要有以下区别:
1、xls是excel 2007之前版本的使用的默认格式。xlsx是excel 2007之后的版本使用的默认格式,包括2007的版本。
2、XLSX格式的占用空间比XLS的小。xlsx是用新的基于XML的压缩文件格式取代了xls默认文件格式。
3、excel 2007之前的版本无法直接打开xlsx格式的,版本较低不兼容。但可通过安装office兼容性补丁包来实现打开编辑。
4、excel 2007之后的版本可以直接打开XLS、xlsx。软件是向下兼容的。如果要使低版本的也可以打开,可以在保存时选择excel 97-2003.xls格式的。

在POI中,解析.XLS使用的是HSSFWorkbook,解析.XLSX的解析用的是XSSFWorkbook,需要判断处理
具体过程:
2. 1 数据库信息的查询。
将数据库中的某张表中的数据进行一个查询,将查询到的数据进行写入excel文件中。
2. 2 建立一张excel表进行存储查询到的数据。
建立一张excel表,首先建立一个工作簿,然后建立一个sheet,在sheet中建立一行作为表头,将数据库查询到的数据分别放到对应的表头的下方。

MultipartFile与File的知识: https://blog.csdn.net/sdut406/article/details/85647982
以下数据库操作不涉及,单纯实现这种思想:
实体封装:(运行结果有问题,但是这里只是单纯的实现功能)

package com.ssn.demo.controller;

import lombok.Data;

@Data
public class User {

private String deptNO;

private String deptName;

private String deptNum;
}

实现的控制层:

package com.ssn.demo.controller;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class TestController {

// 2003版及以前
public static final String EXCEL_XLS = "xls";
// 2007版及以后
private static final String EXCEL_XLSX = "xlsx";

/**
 * 判断是否为excel 
 * @throws Exception 
 */
public void checkExcel(String fileName) throws Exception {
    // 不是文件,且不是EXCEL_XLS和EXCEL_XLSX结尾的
    if (fileName.endsWith(EXCEL_XLS) || fileName.endsWith(EXCEL_XLSX)) {
        throw new Exception("不是excel文件");
    }
} 

/**
 * 判断excel的版本,得到对应的Workbook
 */
public Workbook getWorkbook(InputStream in, MultipartFile file) throws Exception {
    Workbook workbook = null;
    if (file.getName().endsWith(EXCEL_XLS)) { // excel 2003
        workbook = new HSSFWorkbook(in);
    }
    if (file.getName().endsWith(EXCEL_XLSX)) { // excel 2007/10
        workbook = new XSSFWorkbook(in);
    }
    return workbook;
    
}

/**
 * 生成excel并将虚拟好的数据写入
 * @throws IOException 
 */
@RequestMapping("/excelExport")
@ResponseBody
public Object excelExport(HttpServletResponse response) throws IOException {
    // 如果你想操作数据库,也可以操作。这里建议用高版本
    HSSFWorkbook workbook = new HSSFWorkbook(); //Excel的文档对象 2003
    
    // 以下的参数,都可以抽象化,从而实现代码的可读性
    HSSFSheet sheet = workbook.createSheet("excel测试表格"); // 创建sheet表单并命名
    
    // 创建第一行
    HSSFRow row = null; // Excel的行
    row = sheet.createRow(0); // 创建第一行
    row.setHeight((short)(26.25*20)); //设置行高
    row.createCell(0).setCellValue("用户列表");// 为第一个单元格设值
    // 将首行用三个单元格表示,即是合并列(你也可以合并行)
    CellRangeAddress rowRange = new CellRangeAddress(0,0,0,3); 
    sheet.addMergedRegion(rowRange); // 合并整合到sheet
    
    //创建第一行
    row = sheet.createRow(1);
    row.setHeight((short)(22.25*20)); 
    row.createCell(0).setCellValue("部门名ID"); // 为第一个单元格设值
    row.createCell(1).setCellValue("部门名"); // 为第二个单元格设值
    row.createCell(2).setCellValue("部门员工数"); // 为第三个单元格设值
    
    for(int i = 0; i < 6; i++) { // 这里模拟从数据库取出的数据
        row = sheet.createRow(i + 2);
        row.createCell(0).setCellValue(i); 
        row.createCell(1).setCellValue(i); 
        row.createCell(2).setCellValue(i); 
    }
    sheet.setDefaultRowHeight((short)(22.5*20)); // 设置默认行高
    // 列宽自适应
    for(int i = 0; i < 9; i++) {
        sheet.autoSizeColumn(i);
    }
    // 设置输出文件类型为excel文件
    response.setContentType("application/vnd.ms-excel;charset=utf-8");
    //默认Excel名称
    response.setHeader("Content-disposition", "attachment;filename=user.xls");
    OutputStream os = response.getOutputStream();
    workbook.write(os);
    os.flush();
    os.close();
    
    
    return response;
    
}
/**
 * excel文件上传,写入文件
 * 这里模拟生成sql,即可。不做数据库操作
 * @throws Exception 
 */
@RequestMapping("/excelImport")
public String excelImport(@RequestParam(value = "fileName") MultipartFile file, HttpSession session) throws Exception {
    
    boolean flag = false; // 判导入成功的flag
    // MultipartFile理解,看参考
    checkExcel(file.getOriginalFilename());
    try {
        flag = batchImport(file);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null; // 这里返回到页面,没写页面
    
}

@Transactional(readOnly = false , rollbackFor = Exception.class)
private boolean batchImport(MultipartFile file) throws Exception {
    boolean flag = false;
    InputStream is = file.getInputStream();
    Workbook workbook = getWorkbook(is, file);
    
    int sheetCount = workbook.getNumberOfSheets(); // Sheet的数量
    // 这里默认sheet数量只有1个,很可能有多个需要处理,忽略
    Sheet sheet = workbook.getSheetAt(0); // 遍历第一个Sheet
   
    if (sheet == null) {
        flag = true;
    }
    // 为跳过第一行目录设置count = 0
    int count = 0;
    
    // 创建存储对象
    User user = null;
    List users = new ArrayList();
    // 严格与数据库数据对接好,第一个属性是主键
    for(int i = count + 1 ; i <= sheet.getLastRowNum(); i++) { // 取得最后一行设限
        Row row = sheet.getRow(i); // 通过sheet表单,得到行对象
        if (row == null) {
            continue;
        }
        user = new User(); // 每行的存储对象创建
        // 拿到第1行的第一个单元格数据string,这样并不好,代码的重复性并不好,
        // 个人认为如果只是单个业务,下面直接获取的方式或许更好
        // String deptNO = row.getCell(0).getStringCellValue(); 
        // 于是采用这种方式
        List list = new ArrayList();
        for(Cell cell : row) {
            if (cell.toString() == null) {
                continue;
            }
            int cellType = row.getCell(0).getCellType();
            String cellValue = "";
            switch (cellType) {
                case Cell.CELL_TYPE_BLANK: // 空白
                    cellValue = cell.getStringCellValue();
                    break;
                case Cell.CELL_TYPE_BOOLEAN: // 布尔类型
                    // String.valueOf强转和String,toString
                    /**
                     * 1, String标准转换,比如Integer转String就会报错
                     * 2,toStrin按道理一切都可以调用它,但是null去转化时,会报错
                     * 3,String.valueOf这是String的静态方法,他完全的避免了上面的两种情况
                     */
                    cellValue = String.valueOf(cell.getBooleanCellValue());
                    break;
                case Cell.CELL_TYPE_ERROR: // 错误
                    cellValue = "错误";
                    break;
                case Cell.CELL_TYPE_FORMULA: //公式 
                    cellValue = cell.getCellFormula();
                     break;
                case Cell.CELL_TYPE_NUMERIC: // 数字日期
                    if (DateUtil.isCellDateFormatted(cell)) {
                        //约定日期格式
                        cellValue = new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
                    } else {
                        cell.setCellType(Cell.CELL_TYPE_STRING);
                        cellValue = String.valueOf(cell.getRichStringCellValue().getString()) + "#";
                    }
                    break;
                case Cell.CELL_TYPE_STRING:
                    cellValue = cell.getStringCellValue();
                    break;
                default:
                    cellValue ="";
            }
            list.add(cellValue);
        }
        //完整的循环一次 就组成了一个对象
        user.setDeptNO((String)list.get(0));
        user.setDeptName((String)list.get(1));
        user.setDeptNum((String)list.get(2));
        users.add(user);
    }
    // 到此得到表格中的所有东西,接下来是写入数据库
    for(User user1 : users) {
        // 调用写入方法即可,这里不实现,
    }
    
    return false;
}
}

此外,上面我们可以发现,这种写法只适合单个业务,如果你涉及多个业务的excel导出,这样的写显然得不偿失。于是我们引入根据制定好的excel模板,来写入数据。
而且不用考虑是xls还是xlsx。是非常适合大量excel导出的项目
先给出图的效果:我们可以看到这种方式很方便,而且计算还可以利用表达式等

大致思路:有空来补充完整这种实现。
首先,你要对ExcelTransformer有一定了解。你需要去详细了解这个的使用方式
springboot实现文件的输入输出_第1张图片
我们可以看到完美解决了excel版本的问题
springboot实现文件的输入输出_第2张图片
因此你所需要的依赖。


    
        net.sf.jett
        jett-core
        0.11.0
    
    
    
    
        net.sf.jett
        jett
        0.11.0
        pom
    

加入后可看到
springboot实现文件的输入输出_第3张图片
查看该api我们可以看到:(相应的依赖,必须导入)
springboot实现文件的输入输出_第4张图片
这里程序是上班的时候,有空写的。

package com.poi.testpoi.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.poi.testpoi.pojo.User;
import com.poi.testpoi.util.ExcelUtil;

/*
 *  根据模板生成excel文件 
 */

@Controller
public class TemplateToExcelController {

// 实际项目中这一部分写在类似constant.java中
public static final List columnNameList = new ArrayList() {
    {
        add("province");
        add("population");
        add("average");
        add("total");
    }
};
/**
     *  就不去写页面直接请求,简单模拟,采用访问即生成
 * @param request
 * @param response
 */
@RequestMapping("/templateToExcel")
@ResponseBody
public String templateToExcel(HttpServletRequest request, HttpServletResponse response) {
    
    // 实际项目来自配置文件
    String templateFileName = "C:\\Users\\zy962\\Desktop\\test.xlsx";
    String targetFileName = "C:\\Users\\zy962\\Desktop\\testzy.xlsx";
    
    // 构造数据结构beans
    HashMap beans = new HashMap<>();
    HashMap datas = new HashMap<>();
    
    // 模拟构造数据
    String[] province = {"湖北","安徽","重庆"};
    Integer[] population = {2222,3333,4444};
    Integer[] average = {500,600,700};
    
    Integer[] total = new Integer[3];
    for(int i = 0; i < 3; i++) {
        total[i] = population[i]*average[i];
    }
    // 数据植入,下面这种逻辑思维,想了很久,这种赋值方式应该是最好的选择。
    for(int i = 0; i < columnNameList.size(); i++) {
        String name = columnNameList.get(i);
        for(int j = 0; j < 3; j++) {
            String tempName = name.concat("0" + j);
            switch (i) {
                case 0:
                    datas.put(tempName, province[j]);
                    break;
                case 1:
                    datas.put(tempName, population[j]);
                    break;
                case 2:
                    datas.put(tempName, average[j]);
                case 3:
                    datas.put(tempName, total[j]);
                default:
                    break;
            }
        }
    }
    
    // 将datas放入beans,这是很必要的,1,jett要求是这中结构;2,这样才能实现统一取值。
   beans.put("datas", datas);
   ExcelUtil.create(templateFileName, targetFileName, beans); 
   return "成功下载,请去查看";
}
}

转换方法封装的帮助类:

package com.poi.testpoi.util;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;

import net.sf.jett.transform.ExcelTransformer;

/**
 * excel生成类
 * @author zy962
 *
 */
public class ExcelUtil {

/**
     * 根据模板生成excel,这种帮助类
 * @param templateFileName 模板文件
 * @param lists 模板需要的数据,严格于实体中对应
 * @param targetFileName 生成的目标文件
 */
public static void create(String templateFileName, String targetFileName, HashMap beans) {
    //Excel处理,这里最好选择ExcelTransformer可以完美解决excel的xls,xlsx的问题
    ExcelTransformer transformer = new ExcelTransformer();
    try {
        transformer.transform(templateFileName, targetFileName, beans);
    } catch (InvalidFormatException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    };
}
}

2,实现CSV的导入和导出
常识了解:
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。
用途:CSV是逗号分隔文件(Comma Separated Values)的首字母英文缩写,是一种用来存储数据的纯文本格式,通常用于电子表格或数据库软件。在 CSV文件中,数据“栏”以逗号分隔,可允许程序通过读取文件为数据重新创建正确的栏结构,并在每次遇到逗号时开始新的一栏。、

参考:关于reset https://blog.csdn.net/xingkong22star/article/details/39207015
关于三种路径file的getPath getAbsolutePath和getCanonicalPath的不同的知识:http://www.blogjava.net/dreamstone/archive/2007/08/08/134968.html

这里我们还是借用上面的封装对象。

package com.ssn.demo.controller;


import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class CSVTestController {

/**
   * 模拟CSV导出操作
 */
@RequestMapping("/exportCsv")
public void exportCsv(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 得到前台请求的信息 id,这里只处理单文件导出
    String id = request.getParameter("id");
    // 处理请求为null
    if (id == null) {
        throw new Exception("请求为空,请选择要导出的文件名");
    }
    // 数据库请求返回数据,模拟返回为空,如果是实际数据库操作,请加上异常抓取
    List exportList = null;
    
    // 头标题对应的属性标题,这里必须要用LinkedHashMap,因为需要顺序。
    HashMap map = new LinkedHashMap<>();
    map.put("1", "部门NO");
    map.put("2", "部门名称");
    map.put("3", "部门员工数");
    // 属性对应的字段
    String[] fields = new String[] {"deptNO", "deptName", "deptNum"};
    
    // 调用导出方法,z这体现了静态方法的好处,但是不要盲目写态度哦的静态方法,毕竟类加载时加载
    CSVUtils.exportCSVFile(response, map, exportList, fields);
}

/**
 * 
     *  模拟CSV文件导入
 * @param file
 */
@RequestMapping("/importCSVFile")
public void importCSVFile(MultipartFile file) {
    try {
        // 这就得到了该list,想做什么操作对该list对象操作即可
        List lists =  CSVUtils.importCSVFile(file);
        if (lists != null && lists.get(0).split(",")[0].equals("User")) {
            // 为null或数据不匹配user
            throw new Exception("没有数据或数据类型不匹配");
        }
        // 大小限制,这里读进来的没有去表头
        lists.remove(0);
        if (lists.size() > 1000) {
            throw new Exception("超过文件限制1000条");
        }
       
        // 真正的操作,比如写入数据库。
        for(String string : lists) { // 每个值是由","分开的
            // 利用这一点得到单个数据
            String[] values = string.split(",");
            // 比如放置值到User对象中,去mapper做写入操作
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
}

帮助类:

package com.ssn.demo.controller;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.multipart.MultipartFile;

public class CSVUtils {

/**
     * 导出帮助类
 * @param response
 * @param map 头标题对应的属性标题
 * @param exportList 导出的数据
 * @param fields 列
 */
public static void exportCSVFile(HttpServletResponse response, HashMap map, List exportList, String[] fields) {
    try {
        // 创建临时文件
        File tempFile = File.createTempFile("test", ".csv");
        BufferedWriter bf = null;
        // utf-8是正确读取分隔符
        bf = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempFile),"GBK"),1024);
        // map头标题的处理
        for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
            Entry entry = (Entry) it.next();
            bf.write((String)entry.getValue() != null ? new String(((String) entry.getValue()).getBytes("GBK"), "GBK") : "");
            if (it.hasNext()) {
                bf.write(",");
            }
        }
        bf.write("\r\n"); // 到此头标题处理完
        
        // exportList导出的数据写入
                  // exportList导出的数据写入
        for (int j = 0; exportList != null && !exportList.isEmpty() && j < exportList.size(); j++) {
            
            Class clazz = exportList.get(j).getClass();
            String[] contents = new String[fields.length];
            for (int i = 0; fields != null && i < fields.length; i++) {
                String fieldName = toUpperCaseFirstOne(fields[i]);
                Object object = null;
                try {
                    Method method = clazz.getMethod(fieldName);
                    method.setAccessible(true);
                    object = method.invoke(exportList.get(i));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                String str = String.valueOf(object);
                if (str == null || str.equals("null") ) {
                    str = "";
                }
                contents[i] = str;
            }

            // 得到一个数组contents,写入bf
            for(int i = 0; i < contents.length; i++) {
                bf.write(contents[i]);
                bf.write(",");
            }
            bf.write("\r\n"); 
        }
        
        bf.flush(); // 到此已经已经完成数据准备
        
        /**
         * 创建缓冲区,写出
         */
        OutputStream os = response.getOutputStream();
        byte[] datas = new byte[1024];
        // 关于三种路径的知识:http://www.blogjava.net/dreamstone/archive/2007/08/08/134968.html
        File fileDownload = new File(tempFile.getCanonicalPath());
        // 关于reset https://blog.csdn.net/xingkong22star/article/details/39207015
        response.reset();
        response.setContentType("application/csv"); //声明是csv文件
        // 文件名指定
        String CSVName= UUID.randomUUID()+ ".csv";
        response.setHeader("Content-Disposition", "attachment; filename = " + new String(CSVName.getBytes("GBK"),"ISO8559-1"));
        // 文件大小说明,并将long型转为字符串
        String length = String.valueOf(fileDownload.length());
        response.setHeader("Content_Length", length);
        
        // 写入csv结束,写出流
        FileInputStream fis = new FileInputStream(fileDownload);
        int count = 0;
        while ((count = fis.read(datas)) == -1) { // 每次读入1024个字节
            // 写入response的输出流
            os.write(datas, 0 ,count);
        }
        // 关闭完成
        fis.close();
        os.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 首字母小写转大写
private static String toUpperCaseFirstOne(String origin) {
    StringBuffer sb = new StringBuffer(origin);
    sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
    sb.insert(0, "get");
    return sb.toString();
}

/**
     * 导出帮助类
 * @param file 传入CSV文件
 */
public static List importCSVFile(MultipartFile file) {
    
    List list = new ArrayList();
    BufferedReader br = null;
    try {
        br = new BufferedReader(new InputStreamReader(file.getInputStream(),"UTF-8"));
        String line = "";
        while ((line = br.readLine()) != null) {
            list.add(line);
        }
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (br != null) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return list;
}
 
}

3,PDF模板的输出

你可能感兴趣的:(spring系列)