代码 :https://github.com/goodboyQAQ/poi
一.pom文件
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
二.创建实体类
Result类保存一些数据信息返回给前端
@Data //生成 getting 和 setting ,equals、canEqual、hashCode、toString 方法
public class Result<T> {
private String msg;
private boolean success;
private T data;
public Result(){
this.success=false;
this.msg="系统错误";
}
}
Company是我们导入导出的数据实体类
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.wang.poi.annotation.ExcelTitle;
@Data //生成 getting 和 setting ,equals、canEqual、hashCode、toString 方法
//重写hashcode,equals判断时(id,name,tel相同就true)
@EqualsAndHashCode(callSuper = false, exclude = {"updateTime"})
//导入导出需要用到的字段
@ExcelTitle(value={"id","name","tel"},title={"编号(不能修改)","名称","电话"})
public class Company {
private String id;
private String name;
private String tel;
private String updateTime;
}
上面使用了一个自定义的注解
//指明修饰的注解,可以被例如javadoc此类的工具文档化
@Documented
// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Retention(RetentionPolicy.RUNTIME)
// 可作用在接口、类、枚举、注解
@Target(ElementType.TYPE)
public @interface ExcelTitle {
//字段顺序
String[] value();
//中文表头顺序
String[] title();
}
三.controller类
为了方便理解,从顶层开始
@RestController
@Slf4j
public class CompanyController {
@Autowired
private FileUtil fileUtil;
@Autowired
private PoiUtil poiUtil;
@Autowired
private CompanyService companyService;
//下载导入模板
@RequestMapping(value="temp",method=RequestMethod.GET)
public void temp(HttpServletResponse response){
String fileName="company.xlsx"; //传入下载工具类生成下载文件名
try{
//user.xlsx模板文件放在resource/template下
InputStream is=this.getClass().getResourceAsStream("/templates/company.xlsx");
//获取输入流后实现下载
fileUtil.download(is,fileName,response);
}catch(Exception e){
log.error(e.getMessage(),e);
}
}
@RequestMapping(value="upload",method=RequestMethod.POST)
public Result uplaod(@RequestParam("file")MultipartFile file){
//MultipartFile spring支持的处理表单的file很方便
Result result=new Result();
try{
//通过文件获得工作簿
Workbook wb=poiUtil.getWorkBook(file);
//将数据解析为我们的实体类集合
List<Company> list=poiUtil.importExcel(wb,Company.class); //解析导入的数据
companyService.importCompany(list); //存入数据库
result.setMsg("导入成功");
result.setSuccess(true);
}catch(Exception e){
log.error(e.getMessage(),e);
}
return result;
}
@RequestMapping(value="download",method=RequestMethod.GET)
public Result download(Company company,HttpServletResponse response){
Result result=new Result();
try{
//查询数据
List<Company> list=companyService.exportData(company);
if(list.size()==0){
result.setMsg("数据为空");
return result;
}
String fileName="company.xlsx";
InputStream is=this.getClass().getResourceAsStream("/templates/company.xlsx");
poiUtil.exportData(fileName,list,response,Company.class);
result.setSuccess(true);
result.setMsg("导出成功");
}catch(Exception e){
log.error(e.getMessage(),e);
}
return result;
}
}
四.service层
dao数据库查询,就不写了
@Service
public class CompanyServiceImpl implements CompanyService {
@Autowired
private CompanyDao companyDao;
@Override
public void importCompany(List<Company> list) {
if(list.size()==0){
return;
}
List<Company> insertList=new ArrayList<>();
List<Company> updateList=new ArrayList<>();
for(Company company:list){
//空值判断
if(StringUtils.isNotEmpty(company.getId())){
//通过id去数据库查找是否存在该条数据
Company c=companyDao.getCompanyById(company.getId());
//使用lombok的@EqualsAndHashCode重写了hashcode
if(c!=null && !company.equals(c)){ //id查找的数据存在,且有更改
updateList.add(company);
}
}else{
insertList.add(company);
}
}
if(insertList.size()!=0) {
companyDao.addCompanyList(insertList);
}
for(Company company:updateList){
companyDao.updateCompany(company);
}
}
@Override
public List<Company> exportData(Company company) {
return companyDao.exportData(company);
}
}
五.FileUtil工具类
@Slf4j
@Component
public class FileUtil {
/**
* 下载文件
* @param is 输入流
* @param fileName 文件名
* @param response
*/
public void download(InputStream is, String fileName, HttpServletResponse response){
response.setContentType("multipart/form-data");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
byte[] buffer=new byte[1024];
BufferedInputStream bis=null;
OutputStream os=null;
try{
bis=new BufferedInputStream(is);
os=response.getOutputStream();
int i;
while((i=bis.read(buffer))!=-1){
os.write(buffer,0,i);
}
}catch(Exception e){
log.error(e.getMessage(),e);
}finally {
try{
if(bis!=null){
bis.close();
}
}catch (Exception e){
log.error("缓冲输入流关闭异常");
}
try{
if(os!=null){
os.close();
}
}catch (Exception e){
log.error("输出流流关闭异常");
}
}
}
public void upload(MultipartFile file){
if(file.isEmpty()){
return;
}
String fileNmae=file.getOriginalFilename();
String filePath=System.getProperty("user.dir")+"/temp";
File dir=new File(filePath);
if(!dir.exists()){
dir.mkdir();
}
try{
file.transferTo(dir);
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
}
六.POI工具类
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j //可以直接使用日志方法 log.error(...);
public class PoiUtil<T> {
//根据文件后缀生成响应的工作簿,我这里只支持xlsx格式
public Workbook getWorkBook(MultipartFile file) throws Exception{
String fileName = file.getOriginalFilename().toLowerCase();
InputStream is = null;
try {
if (fileName.endsWith("xlsx")) {
return new XSSFWorkbook(file.getInputStream());
} else {
throw new Exception("excel文件类型错误");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception("文件格式错误");
} finally {
try {
if (is != null) {
{
is.close();
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
/**
* 导入
* @param workbook 工作簿
* @param clazz 对应实体类
* @return
*/
public List<T> importExcel(Workbook workbook, Class<T> clazz){
//利用反射获取我在注解里定义好的字段顺序
String[] fields=clazz.getAnnotation(ExcelTitle.class).value();
List<T> list=new ArrayList<>(); //返回的对象列表
//获取第一个工作簿,只支持解析第一个工作簿
Sheet sheet=workbook.getSheetAt(0);
for(Row row:sheet){
//第一次循环 表头跳过
if(row==sheet.getRow(0)){
continue;
}
//第二次往后
try {
T t=clazz.newInstance();
//row.getLastCellNum()获取的不是行数,是下标,所以+1
for(int i=0;i<row.getLastCellNum()+1;i++){
Cell cell=row.getCell(i);
if(cell!=null){
//cell的值类型需要处理
String cellValue=getCellStringVal(cell);
//获取所有字段,包括private
Field f=clazz.getDeclaredField(fields[i]);
//设置为true后才能操作private属性
f.setAccessible(true);
//给该属性设置值
f.set(t,cellValue);
}
}
list.add(t);
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
return list;
}
//导出
public void exportData(String fileName,List<T> list, HttpServletResponse response,Class<T> clazz){
Field[] field=clazz.getDeclaredFields();
Workbook workbook=new XSSFWorkbook();
Sheet sheet=workbook.createSheet();
Row row=null;
try {
//获取注解中定义好的中文表头顺序
String[] title=clazz.getAnnotation(ExcelTitle.class).title();
//获取注解中定义好的字段顺序
String[] fields=clazz.getAnnotation(ExcelTitle.class).value();
for(int i=0;i<=list.size();i++){
row=sheet.createRow(i);
Cell cell=null;
if(i==0){ //第一次创建标题行
for(int j=0;j<title.length;j++){
cell=row.createCell(j);
//安顺序设置excel表头
cell.setCellValue(title[j]);
}
continue;
}
//第二次循环开始设置数据
T t = list.get(i - 1);
for(int j=0;j<fields.length;j++){
Field f=t.getClass().getDeclaredField(fields[j]);
f.setAccessible(true);
cell=row.createCell(j);
if(f.get(t)!=null) { //此条数据的该字段有值
cell.setCellValue(f.get(t).toString());
}
}
}
//输出Excel文件
OutputStream output=response.getOutputStream();
response.reset();
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("multipart/form-data");
workbook.write(output);
output.close();
} catch (Exception e) {
log.error(e.getMessage(),e);
}
}
private String getCellStringVal(Cell cell) {
CellType cellType = cell.getCellTypeEnum();
switch (cellType) {
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue()); //日期型
} else {
// 解决问题:1,科学计数法(如2.6E+10),2,超长小数小数位不一致(如1091.19649281798读取出1091.1964928179796),3,整型变小数(如0读取出0.0)
return NumberToTextConverter.toText(cell.getNumericCellValue());
}
case STRING:
return cell.getStringCellValue();
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
return cell.getCellFormula();
case BLANK:
return "";
case ERROR:
return String.valueOf(cell.getErrorCellValue());
default:
return "";
}
}
七.结束
前后端分离,ajax请求不能下载文件 原因
我主要是通过自定义注解定义好excel需要的字段的顺序,poiUtil传入相应的实体Class,再通过反射获取,自定义注解当然也可以用返回字段顺序的方法替代,我只是学着用用自定义注解,有更灵活的方法还望指教,谢谢。