这里主要是实现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有一定了解。你需要去详细了解这个的使用方式
我们可以看到完美解决了excel版本的问题
因此你所需要的依赖。
net.sf.jett
jett-core
0.11.0
net.sf.jett
jett
0.11.0
pom
加入后可看到
查看该api我们可以看到:(相应的依赖,必须导入)
这里程序是上班的时候,有空写的。
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模板的输出