excel工具类对poi的封装,所以需要导入poi的依赖,我用的是4.1.0版本,如果是4以下,工具里面的poi的相关api需要改变;
导入/导出支持:一对多合并(不限制层级数,但每个类只能有一个子集)、必填字段检测(读取的时候能用到)、排序(导出能用到)、动态隐藏字段(导入导出会忽略该字段)
1、导入依赖
<dependencies>
<dependency>
<groupid>org.apache.poi</groupid>
<artifactid>poi</artifactid>
<version>4.1.0</version>
</dependency>
<dependency>
<groupid>org.apache.poi</groupid>
<artifactid>poi-ooxml</artifactid>
<version>4.1.0</version>
</dependency>2、创建注解iexcel注解
name: 用作excel列名isson:用作判断是否是子集(默认false,如果有子集,在子集成员变量上标注: @iexcel(isson = true))hiddenfield:隐藏字段(默认为:false,当为true时,导入导出会忽略该字段,用法: iexcelutils.hidecolumn( clazz,columnname, target),参数依次为:需要修改的class对象、需要修改的字段名、修改的值,也就是true/false),注意 :iexcelutils.hidecolumn()方法需要用在导入导出前才有效sortno: 排序号(默认为0,不进行排序,如果需要排序,在字段上标注@iexcel(sortno=正整数),从1开始排序)- 注意:如果有子集,子集也是从1开始排序
isnotblank: 设置字段不为空(默认false;在导入时,如果希望某个字段必填不然该条数据就不要读取,就需要在该字段上标注:@iexcel(isnotblank=true);如需要获取没有读取的字段信息,用 iexcelutils.geterrrowinfo()方法可以获取到)booleanformatter:布尔类型字段转换(处理布尔值的转换,注意:请将表示true的标识写在分割符"|“的左边,表示false的标识写在分割符“|”的右边 ;默认"是|否”)dateformat:日期转换(默认:“yyyy/mm/dd”;导入导出时,日期格式化)
package com.ph.excel.annotation;
import com.ph.excel.iexcelutils;
import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;
/**
* excel
*/
@retention(retentionpolicy.runtime)
@target(elementtype.field)
public @interface iexcel {
/**
* 列名称,不能重复
* @return
*/
string name() default "";
/**
* 是否是子集
* @return
*/
boolean isson() default false;
/**
* 隐藏字段
* @return
*/
boolean hiddenfield() default false;
/**
* 排序号
* @return
*/
int sortno() default 0;
/**
* 字段必填,不为空
* @return
*/
boolean isnotblank() default false;
//处理布尔值的转换,注意:请将表示true的标识写在分割符"|"的左边,表示false的标识写在分割符“|”的右边 ;默认"是|否"
string booleanformatter() default "是|否";
/**
* 日期格式化
* @return
*/
string dateformat() default iexcelutils.iexcel_date_default_format;
}3、iexcelutils工具类代码
package com.ph.excel;
import com.ph.excel.annotation.iexcel;
import com.sun.istack.internal.notnull;
import org.apache.poi.hssf.usermodel.hssfdateutil;
import org.apache.poi.hssf.usermodel.hssfworkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.cellrangeaddress;
import org.apache.poi.xssf.usermodel.*;
import javax.servlet.http.httpservletresponse;
import java.io.*;
import java.lang.reflect.*;
import java.math.bigdecimal;
import java.net.urlencoder;
import java.text.decimalformat;
import java.text.simpledateformat;
import java.util.*;
import java.util.concurrent.atomic.atomicboolean;
import java.util.concurrent.atomic.atomicinteger;
import java.util.concurrent.atomic.atomicreference;
import java.util.stream.collectors;
import java.util.stream.stream;
/**
* @classname: iexcelutils
* @description: 读取或导出excel工具类
* @date: 2023/6/1 9:57
* @author: ph9527
* @version: 1.0
*/
public class iexcelutils {
/**
* 获取获取大标题行数时循环的行数
*/
private final static integer find_title_size = 10;
/**
* iexcel与当前exceldate类型共有的日期格式
*/
public final static string iexcel_date_default_format = "yyyy/mm/dd";
/**
* 处理布尔值的转换,注意:请将表示true的标识写在分割符"|"的左边,表示false的标识写在分割符“|”的右边 ;默认"是|否"
*/
public final static string iexcel_boolean_true_default_flag = "\\|";
/**
* 默认标题合并列
*/
public final static int excel_default_title_mergernum = 6;
/**
* 因缺失必填字段为读取的数据信息
*/
private static list<blankrowinfo> errrowinfo;
/**
* excel映射顶级对象class
*/
private static class clazz;
static class classvoiexcelentity {
/**
* 当前所属对象class
*/
private class clazz;
private class parentclass;
//当前等级
private integer levelindex;
/**
* 所有标注iexcel注解的各属性值
*/
private list<classvofieldiexcel> fields;
/**
* 标注iexcel的子集
*/
private classvoiexcelentity sonclassvo;
}
static class classvofieldiexcel {
private string classvofieldname;
private iexcel iexcel;
}
private static class celltitlevalueentity {
private string celltitlename;
private integer cellindex;
//在当前行字段对应对象的第一个元素
private boolean isexcelobjfirstfield;
//在当前行字段对应对象的最后一个元素
private boolean isexcelobjlastfield;
}
private static class mergedrowinfo {
private boolean ismerge;
private integer firstrow;
private integer lastrow;
}
public static class blankrowinfo {
//缺失信息的行号
public long rowno;
//当前对象级别
public integer objlevel;
//缺失信息的列名
public list<string> blanktitlenames;
}
private static class exportcolumn {
private string excelcolumnname;
private string value;
}
private static class exportrow {
private list<exportcolumn> values;
private list<exportrow> sons;
private integer levelindex;
private integer mergenum;
}
private static class titleentity {
private string name;
private integer sortno;
}
/**
* 读取excel文件
*
* @param inputstream 前端上传到后端的文件用的是multipartfile接收,免得转file,就用inputstream做参数
* @param filename 文件名,必填,需要根据文件名判断是否是excel文件以及判断是xlsx或者xls,直接用file.getname()就行了
* @param sourceclass
* @param <t>
* @return
*/
public static <t> list<t> readexcel(@notnull inputstream inputstream, @notnull string filename, @notnull class<t> sourceclass) {
//初始化
clazz = null;
list<t> data = new arraylist<>();
workbook wb = null;
try {
//检测是否是excel文件
if (!isexcelfile(filename)) {
throw new runtimeexception("当前文件不是excel文件");
}
//检测是否是xlsx,如果文件名为空,则默认xlsx
wb = isxlsx(filename) ? new xssfworkbook(inputstream) : new hssfworkbook(inputstream);
} catch (exception e) {
data = new arraylist<>();
throw new runtimeexception("excel文件解析错误:" + e);
}
if (wb != null) {
//给顶级对象赋值
clazz = sourceclass;
errrowinfo = new arraylist<>();
sheet sheet0 = wb.getsheetat(0);
int lastrownum = sheet0.getlastrownum();
//总行数小于等于标题行则代表没有数据,直接返回;
int rowcount = lastrownum + 1;
//获取映射对象字段值
list<string> iexcelnames = new arraylist<>();
classvoiexcelentity classvoiexcelentity = new classvoiexcelentity();
classvoiexcelentity.clazz = clazz;
classvoiexcelentity.levelindex = 0;
setallclassvoiexcelentity(classvoiexcelentity, iexcelnames);
int titlecount = gettitlecount(iexcelnames, sheet0);
if (rowcount <= titlecount) {
return data;
}
//读取数据
try {
readdata(sheet0, titlecount, classvoiexcelentity, data);
} catch (exception e) {
throw new runtimeexception("读取excel数据错误:" + e);
}
}
return data;
}
/**
* 导出excel
*
* @param filename
* @param titlename
* @param response
* @param data
* @param sourceclass
*/
public static void exportexcel(@notnull string filename, @notnull string titlename, @notnull httpservletresponse response, @notnull list data, class sourceclass) throws filenotfoundexception {
int rowindex = 0;
workbook workbook = null;
outputstream outputstream = null;
try {
// 创建一个excel工作簿
workbook = new xssfworkbook();
// 创建一个工作表
xssfsheet sheet = (xssfsheet) workbook.createsheet();
clazz = sourceclass;
//获取映射对象字段值
classvoiexcelentity classvoiexcelentity = new classvoiexcelentity();
classvoiexcelentity.clazz = clazz;
classvoiexcelentity.levelindex = 0;
setallclassvoiexcelentity(classvoiexcelentity, new arraylist<>());
//获取排序好的所有列名
list<string> iexcelnames = new arraylist<>();
getiexcelnames(classvoiexcelentity, iexcelnames);
//获取最后的层级,从0开始的
integer lastindex = getlastlevel(classvoiexcelentity);
sheet.setdefaultcolumnwidth(15);
//设置大标题
if (strisnotblank(titlename)) {
createbigtitle(sheet, iexcelnames, titlename, rowindex);
rowindex++;
}
if (listisnotempty(iexcelnames)) {
//设置小标题
createsmalltitles(sheet, iexcelnames, rowindex);
rowindex++;
if (listisnotempty(data)) {
list<exportrow> exportrows = new arraylist<>();
//设置数据与exportrow的对应关系
setexportdatamapping(classvoiexcelentity, data, exportrows);
//设置数据合并行数
setmergerowforexportrow(exportrows, lastindex);
//将数据输入到excel表格中
setdatatoexcel(exportrows, sheet, iexcelnames, rowindex);
}
}
// 告诉浏览器用什么软件可以打开此文件
response.setheader("content-type", "application/octet-stream;charset=utf-8");
// 下载文件的默认名称
response.setheader("content-disposition", "attachment;filename=" + urlencoder.encode(filename + ".xlsx", "utf-8"));
outputstream = response.getoutputstream();
workbook.write(outputstream);
} catch (exception e) {
e.printstacktrace();
} finally {
if (outputstream != null) {
try {
outputstream.close();
} catch (ioexception e) {
throw new runtimeexception(e);
}
}
if (workbook != null) {
try {
workbook.close();
} catch (ioexception e) {
throw new runtimeexception(e);
}
}
}
}
private static void setdatatoexcel(list<exportrow> exportrows, xssfsheet sheet, list<string> iexcelnames, int startrowindex) {
for (exportrow exportrow : exportrows) {
integer mergenum = exportrow.mergenum;
list<exportcolumn> values = exportrow.values;
xssfrow row = nextrow(sheet, startrowindex);
int cellindex = 0;
boolean mergeflag = !(mergenum == 0 || mergenum == 1);
for (; cellindex < values.size(); cellindex++) {
xssfcell cell = row.createcell(cellindex);
cell.setcellvalue(getvaluefromexportcolumn(iexcelnames.get(cellindex), values));
if (mergeflag) {
int endindex = startrowindex + mergenum - 1;
sheet.addmergedregion(new cellrangeaddress(startrowindex, endindex, cellindex, cellindex));
}
}
list<exportrow> sons = exportrow.sons;
if (listisnotempty(sons)) {
setsondatatoexcel(sons, sheet, iexcelnames, startrowindex, cellindex);
}
if (mergeflag) {
startrowindex += mergenum;
} else {
startrowindex += 1;
}
}
}
/**
* 设置子集的值到excel中
*
* @param sons
* @param sheet
* @param startrowindex
* @param startcellindex
*/
private static void setsondatatoexcel(list<exportrow> sons, xssfsheet sheet, list<string> iexcelnames, int startrowindex, int startcellindex) {
integer rowindex = startrowindex;
for (exportrow exportrow : sons) {
integer cellindex = startcellindex;
integer mergenum = exportrow.mergenum;
list<exportcolumn> values = exportrow.values;
xssfrow row = null;
if (sheet.getrow(rowindex) == null) {
row = nextrow(sheet, rowindex);
} else {
row = sheet.getrow(rowindex);
}
integer endcellindex = values.size() + startcellindex;
//合并标识
boolean mergeflag = !(mergenum == 0 || mergenum == 1);
for (; cellindex < endcellindex; cellindex++) {
xssfcell cell = row.createcell(cellindex);
cell.setcellvalue(getvaluefromexportcolumn(iexcelnames.get(cellindex), values));
if (mergeflag) {
int endindex = rowindex + mergenum - 1;
sheet.addmergedregion(new cellrangeaddress(rowindex, endindex, cellindex, cellindex));
}
}
list<exportrow> sons1 = exportrow.sons;
if (mergeflag) {
rowindex += mergenum;
} else {
rowindex += 1;
}
if (listisnotempty(sons1)) {
setsondatatoexcel(sons1, sheet, iexcelnames, row.getrownum(), startcellindex + values.size());
}
}
}
/**
* 根据列名获取值
*
* @param name
* @param values
* @return
*/
private static string getvaluefromexportcolumn(string name, list<exportcolumn> values) {
for (exportcolumn exportcolumn : values) {
if (name.equals(exportcolumn.excelcolumnname)) {
return exportcolumn.value;
}
}
return null;
}
/**
* 获取最后的层级数
*
* @param classvoiexcelentity
* @return
*/
private static integer getlastlevel(classvoiexcelentity classvoiexcelentity) {
classvoiexcelentity sonclassvo = classvoiexcelentity.sonclassvo;
if (sonclassvo == null) {
return classvoiexcelentity.levelindex;
} else {
return getlastlevel(sonclassvo);
}
}
/**
* 设置合并行数
*
* @param exportrows
* @param lastindex
*/
private static void setmergerowforexportrow(list<exportrow> exportrows, integer lastindex) {
for (exportrow exportrow : exportrows) {
list<exportrow> sons = exportrow.sons;
if (lastindex == 0) {
exportrow.mergenum = 1;
} else {
exportrow.mergenum = getmergenum(sons, lastindex);
if (listisnotempty(sons)) {
setmergerowforexportrow(sons, lastindex);
}
}
}
}
private static int getmergenum(list<exportrow> sons, integer lastindex) {
if (listisnotempty(sons)) {
exportrow exportrow = sons.get(0);
if (exportrow.levelindex == lastindex - 1) {
atomicinteger count = new atomicinteger();
sons.foreach(item -> {
list<exportrow> sons1 = item.sons;
if (listisnotempty(sons1)) {
count.addandget(sons1.size());
}
});
return count.get();
} else if (exportrow.levelindex == lastindex) {
return sons.size();
} else {
atomicinteger flag = new atomicinteger();
sons.foreach(item -> {
flag.addandget(getmergenum(item.sons, lastindex));
});
return flag.get();
}
}
return 0;
}
/**
* 获取所有excel列名,并按顺序排列
*
* @param classvoiexcelentity
* @return
*/
private static void getiexcelnames(classvoiexcelentity classvoiexcelentity, list<string> names) {
if (classvoiexcelentity != null) {
list<classvofieldiexcel> fields = classvoiexcelentity.fields;
classvoiexcelentity sonclassvo = classvoiexcelentity.sonclassvo;
if (listisnotempty(fields)) {
list<iexcel> excelssorted = new arraylist<>();
list<iexcel> excels = new arraylist<>();
for (int i = 0; i < fields.size(); i++) {
classvofieldiexcel classvofieldiexcel = fields.get(i);
iexcel iexcel = classvofieldiexcel.iexcel;
if (iexcel != null && !iexcel.isson()) {
if (iexcel.sortno() == 0) {
excels.add(iexcel);
} else {
excelssorted.add(iexcel);
}
}
}
//合并
list<string> newnames = stream.concat(
excelssorted.stream().sorted(comparator.comparingint(iexcel::sortno)).map(item -> item.name()),
excels.stream().map(item -> item.name())
).collect(collectors.tolist());
names.addall(newnames);
}
//是否是有集
if (sonclassvo != null) {
getiexcelnames(classvoiexcelentity.sonclassvo, names);
}
}
}
/**
* 设置数据与excel每列的对应关系
*
* @param classvoiexcelentity
* @param data
* @param exportrows
*/
private static void setexportdatamapping(classvoiexcelentity classvoiexcelentity, list data, list<exportrow> exportrows) {
for (int i = 0; i < data.size(); i++) {
exportrow exportrow = new exportrow();
list<exportrow> sonexportrow = null;
object obj = data.get(i);
class<?> objclass = obj.getclass();
classvoiexcelentity classentity = getclassentitybyclass(classvoiexcelentity, objclass);
if (classentity != null) {
list<classvofieldiexcel> fieldiexcels = classentity.fields;
list<exportcolumn> columnlist = new arraylist<>();
fieldiexcels.foreach(fieldiexcel -> {
try {
if (!fieldiexcel.iexcel.isson()) {
field field = objclass.getdeclaredfield(fieldiexcel.classvofieldname);
if (field != null) {
field.setaccessible(true);
exportcolumn exportcolumn = new exportcolumn();
exportcolumn.value = getfieldvalue(obj, field, fieldiexcel.iexcel);
exportcolumn.excelcolumnname = fieldiexcel.iexcel.name();
columnlist.add(exportcolumn);
}
}
} catch (exception e) {
throw new runtimeexception(e);
}
});
exportrow.values = columnlist;
exportrow.levelindex = classentity.levelindex;
classvoiexcelentity sonclassvo = classentity.sonclassvo;
if (sonclassvo != null) {
sonexportrow = new arraylist<>();
list sondata = getsondata(obj);
if (listisnotempty(sondata)) {
setexportdatamapping(classvoiexcelentity, sondata, sonexportrow);
exportrow.sons = sonexportrow;
}
}
exportrows.add(exportrow);
}
}
}
/**
* 获取对象的属性值
*
* @param obj
* @param <t>
* @return
*/
private static <t> string getfieldvalue(object obj, field field, iexcel annotation) throws exception {
string value = null;
field.setaccessible(true);
if (field.gettype() == boolean.class || field.gettype() == boolean.class) {
if (annotation != null) {
string s = annotation.booleanformatter();
string[] split = s.split(iexcel_boolean_true_default_flag);
object o = field.get(obj);
if (o != null) {
boolean aboolean = (boolean) field.get(obj);
value = aboolean ? split[0] : split[1];
} else {
value = "";
}
}
} else if (field.gettype() == date.class) {
if (annotation != null) {
string dateformatter = annotation.dateformat();
object o = field.get(obj);
if (o != null) {
date date = (date) field.get(obj);
simpledateformat simpledateformat = new simpledateformat(dateformatter);
value = simpledateformat.format(date);
} else {
value = "";
}
}
} else {
object o = field.get(obj);
value = o != null ? o.tostring() : "";
}
return value;
}
/**
* 获取子集
*
* @param obj
* @return
*/
private static list getsondata(object obj) {
field[] declaredfields = obj.getclass().getdeclaredfields();
for (field field : declaredfields) {
class<?> type = field.gettype();
if (type == list.class) {
iexcel annotation = field.getannotation(iexcel.class);
if (annotation != null && annotation.isson()) {
try {
field.setaccessible(true);
return (list) field.get(obj);
} catch (illegalaccessexception e) {
throw new runtimeexception(e);
}
}
}
}
return null;
}
/**
* 根据class获取对应的classvoiexcelentity
*
* @param classvoiexcelentity
* @param aclass
* @return
*/
private static classvoiexcelentity getclassentitybyclass(classvoiexcelentity classvoiexcelentity, class<?> aclass) {
if (classvoiexcelentity == null) return null;
class clazz1 = classvoiexcelentity.clazz;
if (clazz1.gettypename().equals(aclass.gettypename())) return classvoiexcelentity;
classvoiexcelentity sonclassvo = classvoiexcelentity.sonclassvo;
return getclassentitybyclass(sonclassvo, aclass);
}
/**
* 创建大标题样式
*
* @param wb
* @return
*/
public static xssfcellstyle createbigtitlecellstyle(xssfworkbook wb) {
// 标题样式(加粗,垂直居中)
xssfcellstyle titlecellstyle = wb.createcellstyle();
titlecellstyle.setalignment(horizontalalignment.center);//水平居中
titlecellstyle.setverticalalignment(verticalalignment.center);//垂直居中
xssffont fontstyle = wb.createfont();
fontstyle.setbold(true); //加粗
fontstyle.setfontheightinpoints((short) 16); //设置标题字体大小
titlecellstyle.setfont(fontstyle);
return titlecellstyle;
}
/**
* 创建小标题样式
*
* @param wb
* @return
*/
public static xssfcellstyle createsmalltitlecellstyle(xssfworkbook wb) {
//设置表头样式,表头居中
xssfcellstyle style = wb.createcellstyle();
//设置单元格样式
style.setalignment(horizontalalignment.center);
style.setverticalalignment(verticalalignment.center);
//设置字体
xssffont font = wb.createfont();
font.setfontheightinpoints((short) 14);
style.setfont(font);
return style;
}
/**
* 设置大标题
*
* @param sheet
* @param iexcelnames
* @param titlename
* @param rowindex
*/
private static void createbigtitle(xssfsheet sheet, list<string> iexcelnames, string titlename, int rowindex) {
xssfcellstyle style = createbigtitlecellstyle(sheet.getworkbook());
xssfrow rowbigtitle = nextrow(sheet, rowindex);
//大标题
xssfcell cell = rowbigtitle.createcell(0);
cell.setcellvalue(titlename);
cell.setcellstyle(style);
sheet.addmergedregion(new cellrangeaddress(0, 0, 0, listisnotempty(iexcelnames) ? iexcelnames.size() - 1 : excel_default_title_mergernum));
}
/**
* 设置小标题
*
* @param sheet
* @param iexcelnames
* @param rowindex
*/
private static void createsmalltitles(xssfsheet sheet, list<string> iexcelnames, int rowindex) {
xssfcellstyle style = createsmalltitlecellstyle(sheet.getworkbook());
xssfrow rowsmalltitle = nextrow(sheet, rowindex);
for (int i = 0; i < iexcelnames.size(); i++) {
xssfcell cell = rowsmalltitle.createcell(i);
cell.setcellvalue(iexcelnames.get(i));
cell.setcellstyle(style);
}
}
/**
* 获取下一行
*
* @param sheet
* @param rowindex
* @return
*/
private static xssfrow nextrow(xssfsheet sheet, int rowindex) {
xssfrow row = sheet.createrow(rowindex);
return row;
}
/**
* 读取数据
*
* @param sheet
* @param titlecount
* @param classvoiexcelentity
* @param data
* @param <t>
*/
private static <t> void readdata(sheet sheet, integer titlecount, classvoiexcelentity classvoiexcelentity, list<t> data) throws exception {
//定义一个数组,存放各个层级的当前对象
list levelsobjs = new arraylist();
//获取excel的所有小标题
list<celltitlevalueentity> celltitles = getcelltitles(sheet, titlecount);
if (listisnotempty(celltitles)) {
//设置哪些是当前行的第一个元素
setfirstandlastfield(celltitles, classvoiexcelentity);
//开始读取数据
int startdataindex = titlecount + 1;
int allrownums = sheet.getlastrownum() + 1;
try {
//循环excel每一行
for (int i = startdataindex; i < allrownums; i++) {
row row = sheet.getrow(i);
if (row != null && checkrowdataisnotblank(celltitles, row)) {
for (int j = 0; j < celltitles.size(); j++) {
celltitlevalueentity celltitlevalueentity = celltitles.get(j);
integer nowcellindex = celltitlevalueentity.cellindex;
cell cell = row.getcell(nowcellindex);
if (cell == null) {
cell = row.createcell(nowcellindex);
}
string cellvalue = getcellvalue(cell);
//获取当前字段的所属class
classvoiexcelentity classvoiexcelentitynow = getclassvoexcelentitybyexcelcelltitle(celltitlevalueentity.celltitlename, classvoiexcelentity);
integer levelindex = classvoiexcelentitynow.levelindex;
if (classvoiexcelentitynow != null) {
//当前单元格是否为第一个字段
boolean isexcelobjfirstfield = celltitlevalueentity.isexcelobjfirstfield;
boolean isexcelobjlastfieldfield = celltitlevalueentity.isexcelobjlastfield;
//获取合并信息
mergedrowinfo mergedrowinfo = getmergedrowinfo(cell, sheet);
boolean ismerge = mergedrowinfo.ismerge;
integer firstrow = mergedrowinfo.firstrow;
integer lastrow = mergedrowinfo.lastrow;
//判断是否需要新建对象
if (isexcelobjfirstfield && (!ismerge || i == firstrow)) {
if (!listisnotempty(levelsobjs) || levelsobjs.size() < levelindex + 1) {
levelsobjs.add(classvoiexcelentitynow.clazz.newinstance());
} else {
levelsobjs.set(levelindex, classvoiexcelentitynow.clazz.newinstance());
}
}
//获取当前对象
object nowobj = levelsobjs.get(classvoiexcelentitynow.levelindex);
//获取当前单元格对应对象的字段名
string fieldname = getfieldnamebycelltitlename(celltitlevalueentity.celltitlename, classvoiexcelentitynow);
//给属性设置值
if (strisnotblank(cellvalue)) {
setcellvaluetoobjfield(nowobj, fieldname, cellvalue);
}
//获取当前对象的上级对象
object parentobj = getparentobj(classvoiexcelentitynow, levelsobjs);
list sonlist = getsonlistbyparentobj(parentobj);
//判断当前单元格是否是所属对象最后的一个对应,借此判断当前行当前对象是否填充完成
if (isexcelobjlastfieldfield && (!ismerge || i == lastrow)) {
//判断必填字段是否都有值
blankrowinfo blankrowinfo = checkobjisnotblankfield(classvoiexcelentitynow, nowobj, (long) (i + 1));
if (blankrowinfo == null) {
if (parentobj != null) {
sonlist.add(nowobj);
} else {
data.add((t) nowobj);
}
} else {
errrowinfo.add(blankrowinfo);
setsonfieldvaluetonull(nowobj);
break;
}
}
}
}
}
}
} catch (exception e) {
e.printstacktrace();
}
}
}
/**
* 获取未被读取的错误信息
*
* @return
*/
public static list<blankrowinfo> geterrrowinfo() {
return errrowinfo;
}
/**
* 设置子集值为空
*
* @param nowobj
*/
private static void setsonfieldvaluetonull(object nowobj) {
if (nowobj != null) {
class<?> parentobjclass = nowobj.getclass();
field[] declaredfields = parentobjclass.getdeclaredfields();
for (field field : declaredfields) {
iexcel annotation = field.getannotation(iexcel.class);
if (annotation != null) {
if (annotation.isson()) {
field.setaccessible(true);
class<?> type = field.gettype();
if (type == list.class) {
try {
field.set(nowobj, null);
} catch (illegalaccessexception e) {
throw new runtimeexception(e);
}
}
}
}
}
}
}
/**
* 检查当前对象必填字段是否都有值
*
* @param classvoiexcelentitynow
* @param nowobj
*/
private static blankrowinfo checkobjisnotblankfield(classvoiexcelentity classvoiexcelentitynow, object nowobj, long rowno) {
atomicreference<blankrowinfo> blankrowinfo = new atomicreference<>();
list<classvofieldiexcel> fields = classvoiexcelentitynow.fields;
if (nowobj != null && listisnotempty(fields)) {
class<?> clazz = nowobj.getclass();
field[] declaredfields = clazz.getdeclaredfields();
arrays.stream(declaredfields).foreach(field -> {
field.setaccessible(true);
try {
object fieldvalueobj = field.get(nowobj);
iexcel iexcel = field.getannotation(iexcel.class);
if (fieldvalueobj == null && iexcel != null && iexcel.isnotblank()) {
if (blankrowinfo.get() == null) {
blankrowinfo.set(new blankrowinfo());
blankrowinfo.get().rowno = rowno;
blankrowinfo.get().objlevel = classvoiexcelentitynow.levelindex;
blankrowinfo.get().blanktitlenames = new arraylist<>();
}
blankrowinfo.get().blanktitlenames.add(iexcel.name());
}
} catch (illegalaccessexception e) {
throw new runtimeexception(e);
}
});
}
return blankrowinfo.get();
}
/**
* 获取上级对象的子集
*
* @param parentobj
* @return
*/
private static list getsonlistbyparentobj(object parentobj) {
if (parentobj != null) {
class<?> parentobjclass = parentobj.getclass();
field[] declaredfields = parentobjclass.getdeclaredfields();
for (field field : declaredfields) {
iexcel annotation = field.getannotation(iexcel.class);
if (annotation != null) {
if (annotation.isson()) {
field.setaccessible(true);
class<?> type = field.gettype();
if (type == list.class) {
try {
object son = field.get(parentobj);
if (son != null) {
return (list) son;
}
list sonlist = new arraylist<>();
field.set(parentobj, sonlist);
return sonlist;
} catch (illegalaccessexception e) {
throw new runtimeexception(e);
}
}
}
}
}
}
return null;
}
/**
* 获取父级对象
*
* @param classvoiexcelentitynow
* @param levelsobjs
* @return
*/
private static object getparentobj(classvoiexcelentity classvoiexcelentitynow, list levelsobjs) {
object parentobj = null;
class parentclass = classvoiexcelentitynow.parentclass;
if (parentclass != null) {
string typename = parentclass.gettypename();
for (object item : levelsobjs) {
if (typename.equals(item.getclass().gettypename())) {
parentobj = item;
break;
}
}
}
return parentobj;
}
/**
* 获取当前单元格对应对象的字段名
*
* @param celltitlename
* @param classvoiexcelentitynow
* @return
*/
private static string getfieldnamebycelltitlename(string celltitlename, classvoiexcelentity classvoiexcelentitynow) {
list<classvofieldiexcel> fields = classvoiexcelentitynow.fields;
for (classvofieldiexcel classvofieldiexcel : fields) {
if (classvofieldiexcel.iexcel.name().equals(celltitlename)) {
return classvofieldiexcel.classvofieldname;
}
}
return null;
}
/**
* 给属性设置值
*
* @param obj
* @param filedname
* @param cellvalue
* @param <t>
*/
private static <t> void setcellvaluetoobjfield(object obj, string filedname, object cellvalue) throws exception {
class<?> clazz = obj.getclass();
field declaredfield = clazz.getdeclaredfield(filedname);
declaredfield.setaccessible(true);
class<?> type = declaredfield.gettype();
if (type == integer.class || type == int.class) {
declaredfield.set(obj, integer.parseint(cellvalue.tostring()));
} else if (type == long.class || type == long.class) {
declaredfield.set(obj, long.parselong(cellvalue.tostring()));
} else if (type == boolean.class || type == boolean.class) {
iexcel annotation = declaredfield.getannotation(iexcel.class);
string s = annotation.booleanformatter();
string[] split = s.split(iexcel_boolean_true_default_flag);
declaredfield.set(obj, split[0].trim().equals(cellvalue.tostring().trim()) ? true : false);
} else if (type == date.class) {
iexcel annotation = declaredfield.getannotation(iexcel.class);
string s = annotation.dateformat();
simpledateformat format = new simpledateformat(s);
declaredfield.set(obj, format.parse(cellvalue.tostring()));
} else if (type == double.class || type == double.class) {
declaredfield.set(obj, double.valueof(cellvalue.tostring()));
} else if (type == list.class) {
declaredfield.set(obj, cellvalue);
} else if (type == bigdecimal.class) {
bigdecimal bigdecimal = bigdecimal.valueof(long.valueof(cellvalue.tostring()));
declaredfield.set(obj, bigdecimal);
} else {
declaredfield.set(obj, cellvalue);
}
}
/**
* 设置哪些是当前行的第一个元素和最后一个元素 (注意:这里要求不能出现重复的列名)
*
* @param celltitles
* @param classvoiexcelentity
*/
private static void setfirstandlastfield(list<celltitlevalueentity> celltitles, classvoiexcelentity classvoiexcelentity) {
final classvoiexcelentity[] previousclassvo = {null};
for (int i = 0; i < celltitles.size(); i++) {
celltitlevalueentity item = celltitles.get(i);
string celltitlename = item.celltitlename;
classvoiexcelentity nowclassvoentity = getclassentitybycelltitlename(celltitlename, classvoiexcelentity);
if (nowclassvoentity != null) {
//设置是否是第一个元素
if (previousclassvo[0] == null) {
item.isexcelobjfirstfield = true;
previousclassvo[0] = nowclassvoentity;
} else if (!previousclassvo[0].clazz.gettypename().equals(nowclassvoentity.clazz.gettypename())) {
item.isexcelobjfirstfield = true;
previousclassvo[0] = nowclassvoentity;
}
//设置是否是最后一个元素
if (i < celltitles.size() - 1) {
celltitlevalueentity nextitem = celltitles.get(i + 1);
string nextcelltitlename = nextitem.celltitlename;
classvoiexcelentity nextclassvoentity = getclassentitybycelltitlename(nextcelltitlename, classvoiexcelentity);
if (nextclassvoentity != null) {
if (!nowclassvoentity.clazz.gettypename().equals(nextclassvoentity.clazz.gettypename())) {
item.isexcelobjlastfield = true;
}
}
}
if (i == celltitles.size() - 1) {
item.isexcelobjlastfield = true;
}
}
}
}
/**
* 根据列名获取 classvoiexcelentity
*
* @param celltitlename
* @param classvoiexcelentity
* @return
*/
private static classvoiexcelentity getclassentitybycelltitlename(string celltitlename, classvoiexcelentity classvoiexcelentity) {
list<classvofieldiexcel> fields = classvoiexcelentity.fields;
for (classvofieldiexcel item : fields) {
iexcel iexcel = item.iexcel;
string name = iexcel.name();
if (name.equals(celltitlename)) {
return classvoiexcelentity;
}
}
classvoiexcelentity sonclassvo = classvoiexcelentity.sonclassvo;
if (sonclassvo != null) {
return getclassentitybycelltitlename(celltitlename, sonclassvo);
}
return null;
}
/**
* 获取当前字段所属excel映射对象
*
* @param celltitlename
* @param classvoiexcelentity
* @return
*/
private static classvoiexcelentity getclassvoexcelentitybyexcelcelltitle(string celltitlename, classvoiexcelentity classvoiexcelentity) {
classvoiexcelentity rsclassvoiexcelentity = null;
list<classvofieldiexcel> fields = classvoiexcelentity.fields;
for (classvofieldiexcel classvofieldiexcel : fields) {
iexcel iexcel = classvofieldiexcel.iexcel;
string name = iexcel.name();
if (name.equals(celltitlename)) {
rsclassvoiexcelentity = classvoiexcelentity;
break;
}
}
if (rsclassvoiexcelentity == null && classvoiexcelentity.sonclassvo != null) {
rsclassvoiexcelentity = getclassvoexcelentitybyexcelcelltitle(celltitlename, classvoiexcelentity.sonclassvo);
}
return rsclassvoiexcelentity;
}
/**
* 获取标题总行数
* iexcelnames
*
* @param sheet0
* @return
*/
private static int gettitlecount(list<string> iexcelnames, sheet sheet0) {
for (int i = 0; i < find_title_size; i++) {
row row = sheet0.getrow(i);
if (row != null) {
int lastcellnum = row.getlastcellnum();
for (int j = 0; j < lastcellnum; j++) {
cell cell = row.getcell(j);
string cellvalue = getcellvalue(cell);
if (strisnotblank(cellvalue) && iexcelnames.contains(cellvalue)) {
return i;
}
}
}
}
return 0;
}
/**
* 获取合并行信息
*
* @param cell
* @param sheet
* @return
*/
private static mergedrowinfo getmergedrowinfo(cell cell, sheet sheet) {
mergedrowinfo mergedrowinfo = new mergedrowinfo();
for (cellrangeaddress region : sheet.getmergedregions()) {
if (region.isinrange(cell.getrowindex(), cell.getcolumnindex())) {
mergedrowinfo.ismerge = true;
mergedrowinfo.firstrow = region.getfirstrow();
mergedrowinfo.lastrow = region.getlastrow();
return mergedrowinfo;
}
}
mergedrowinfo.ismerge = false;
return mergedrowinfo;
}
/**
* 检查当前行不是空的
*
* @param celltitles
* @param row
* @return
*/
private static boolean checkrowdataisnotblank(list<celltitlevalueentity> celltitles, row row) {
atomicinteger flag = new atomicinteger();
for (celltitlevalueentity title : celltitles) {
integer cellindex = title.cellindex;
cell cell = row.getcell(cellindex);
if (cell != null) {
mergedrowinfo mergedrowinfo = getmergedrowinfo(cell, row.getsheet());
if (mergedrowinfo != null && mergedrowinfo.ismerge) {
return true;
}
if (strisnotblank(getcellvalue(cell))) {
flag.getandincrement();
}
}
}
return flag.get() > 0;
}
/**
* 获取所有小标题
*
* @param sheet
* @param titlecount
* @return
*/
private static list<celltitlevalueentity> getcelltitles(sheet sheet, integer titlecount) {
list<celltitlevalueentity> celltitles = new arraylist<>();
row celltitlerow = sheet.getrow(titlecount);
if (celltitlerow != null) {
int lastcellnum = celltitlerow.getlastcellnum();
for (int i = 0; i < lastcellnum; i++) {
cell cell = celltitlerow.getcell(i);
string cellvalue = getcellvalue(cell);
if (strisnotblank(cellvalue)) {
celltitlevalueentity celltitleentity = new celltitlevalueentity();
celltitleentity.celltitlename = cellvalue;
celltitleentity.cellindex = i;
celltitles.add(celltitleentity);
}
}
}
return celltitles;
}
/**
* 获取excel表格单元格的值
*
* @param cell
* @return
*/
private static string getcellvalue(cell cell) {
string cellvalue = "";
if (cell != null) {
// 判断当前cell的type
switch (cell.getcelltype()) {
// 如果当前cell的type为numeric
case numeric:
case formula: {
// 判断当前的cell是否为date
if (hssfdateutil.iscelldateformatted(cell)) {
date date = cell.getdatecellvalue();
simpledateformat sdf = new simpledateformat(iexcel_date_default_format);
cellvalue = sdf.format(date);
}
// 如果是纯数字
else {
// 取得当前cell的数值
cellvalue = new decimalformat("0").format(cell.getnumericcellvalue());
}
break;
}
// 如果当前cell的type为strin
case string:
// 取得当前的cell字符串
cellvalue = cell.getrichstringcellvalue().getstring();
break;
// 默认的cell值
default:
cellvalue = " ";
}
} else {
cellvalue = "";
}
return cellvalue;
}
/**
* 设置所有iexcel注解与目标对象的映射字段名和值
*
* @param classvoiexcelentity classvoiexcelentity.clazz必须有值
* @param iexcelnames
*/
private static void setallclassvoiexcelentity(classvoiexcelentity classvoiexcelentity, list<string> iexcelnames) {
atomicboolean issonflag = new atomicboolean(false);
class nowclazz = classvoiexcelentity.clazz;
list<classvofieldiexcel> classvofields = new arraylist<>();
field[] declaredfields = nowclazz.getdeclaredfields();
arrays.stream(declaredfields).foreach(field -> {
iexcel iexcel = field.getannotation(iexcel.class);
if (iexcel != null && !iexcel.hiddenfield()) {
classvofieldiexcel classvofieldiexcel = new classvofieldiexcel();
if (iexcel.isson() && !issonflag.get()) {
//处理子集
class sonclass = getgenericclassforlist(field);
if (sonclass != null) {
classvoiexcelentity sonclassvoiexcel = new classvoiexcelentity();
sonclassvoiexcel.clazz = sonclass;
sonclassvoiexcel.parentclass = classvoiexcelentity.clazz;
sonclassvoiexcel.levelindex = classvoiexcelentity.levelindex + 1;
setallclassvoiexcelentity(sonclassvoiexcel, iexcelnames);
classvoiexcelentity.sonclassvo = sonclassvoiexcel;
issonflag.set(true);
}
} else {
iexcelnames.add(iexcel.name());
}
classvofieldiexcel.classvofieldname = field.getname();
classvofieldiexcel.iexcel = iexcel;
classvofields.add(classvofieldiexcel);
}
});
classvoiexcelentity.fields = classvofields;
}
/**
* 获取list的泛型类型
*
* @param field
* @return
*/
private static class getgenericclassforlist(field field) {
class clazz = null;
if (field.gettype() == list.class) {
type generictype = field.getgenerictype();
if (generictype != null && generictype instanceof parameterizedtype) {
parameterizedtype pt = (parameterizedtype) generictype;
clazz = (class<?>) pt.getactualtypearguments()[0];
}
}
return clazz;
}
/**
* 判断是否是excel文件
*
* @param filename
* @return
*/
private static boolean isexcelfile(string filename) {
if (!strisnotblank(filename)) {
return false;
}
string filenamesuffix = getfilenamesuffix(filename);
if (filenamesuffix.tolowercase().equals("xlsx") || filenamesuffix.tolowercase().equals("xls")) {
return true;
} else {
return false;
}
}
/**
* 检测excel类型,如果filename为空,则默认为xlsx
*
* @param filename
* @return
*/
private static boolean isxlsx(string filename) {
if (!strisnotblank(filename)) {
return true;
}
string filenamesuffix = getfilenamesuffix(filename);
if (filenamesuffix.tolowercase().equals("xlsx")) {
return true;
} else {
return false;
}
}
/**
* 获取文件后缀名
*
* @param filename
* @return
*/
private static string getfilenamesuffix(string filename) {
string[] split = filename.split("\\u002e");
string filenamesuffix = split[split.length - 1];
return filenamesuffix;
}
/**
* 检测字符串不为空
*
* @param str
* @return
*/
private static boolean strisnotblank(string str) {
return !(str == null || str.trim().equals(""));
}
/**
* 判断list不为空
*
* @param list
* @return
*/
private static boolean listisnotempty(list list) {
return list != null && list.size() > 0;
}
/**
* 隐藏字段
* @param clazz
* @param columnname
* @param target
* @throws exception
*/
public static void hidecolumn(class<?> clazz, string columnname, boolean target) throws exception {
if (!strisnotblank(columnname)) {
throw new nullpointerexception("传入的属性列名为空");
}
if (target == null) {
target = true;
}
//获取目标对象的属性值
field field = clazz.getdeclaredfield(columnname);
//获取注解反射对象
iexcel excelanion = field.getannotation(iexcel.class);
//获取代理
invocationhandler invocationhandler = proxy.getinvocationhandler(excelanion);
field excelfield = invocationhandler.getclass().getdeclaredfield("membervalues");
excelfield.setaccessible(true);
map membervalues = (map) excelfield.get(invocationhandler);
membervalues.put("hiddenfield", target);
}
}
4、测试
a. 测试的实体类:订单、订单明细、追溯码
- ordermodel
package com.ph.excel.vo;
import com.ph.excel.annotation.iexcel;
import java.util.date;
import java.util.list;
/**
* @classname: ordermodel
* @description: todo
* @date: 2023/6/2 14:31
* @author: ph9527
* @version: 1.0
*/
public class ordermodel {
private long id;
@iexcel(name = "订单号",isnotblank = true)
private string orderno;
@iexcel(name = "订单时间", sortno = 2)
private date ordertime;
@iexcel(name = "购买人", sortno = 1)
private string buyuser;
@iexcel(isson = true)
private list<detailmodel> details;
public ordermodel() {
}
public ordermodel(long id, string orderno, date ordertime, string buyuser, integer buynum, list<detailmodel> details) {
this.id = id;
this.orderno = orderno;
this.ordertime = ordertime;
this.buyuser = buyuser;
this.details = details;
}
@override
public string tostring() {
return "ordermodel{" +
"id=" + id +
", orderno='" + orderno + '\'' +
", ordertime=" + ordertime +
", buyuser='" + buyuser + '\'' +
", details=" + details +
'}';
}
public long getid() {
return id;
}
public void setid(long id) {
this.id = id;
}
public string getorderno() {
return orderno;
}
public void setorderno(string orderno) {
this.orderno = orderno;
}
public date getordertime() {
return ordertime;
}
public void setordertime(date ordertime) {
this.ordertime = ordertime;
}
public string getbuyuser() {
return buyuser;
}
public void setbuyuser(string buyuser) {
this.buyuser = buyuser;
}
public list<detailmodel> getdetails() {
return details;
}
public void setdetails(list<detailmodel> details) {
this.details = details;
}
}
- detailmodel:
package com.ph.excel.vo;
import com.ph.excel.annotation.iexcel;
import java.util.date;
import java.util.list;
/**
* @classname: detailmodel
* @description: todo
* @date: 2023/6/2 14:31
* @author: ph9527
* @version: 1.0
*/
public class detailmodel {
private long id;
private long orderid;
@iexcel(name = "商品名")
private string goodname;
@iexcel(name = "商品码", isnotblank = true, sortno = 2)
private string goodcode;
@iexcel(name = "购买数量", sortno = 1)
private integer buynum;
@iexcel(isson = true)
private list<codemodel> codes;
@override
public string tostring() {
return "detailmodel{" +
"id=" + id +
", orderid=" + orderid +
", goodname='" + goodname + '\'' +
", goodcode='" + goodcode + '\'' +
", buynum=" + buynum +
", codes=" + codes +
'}';
}
public detailmodel() {
}
public detailmodel(long id, long orderid, string goodname, string goodcode, integer buynum, list<codemodel> codes) {
this.id = id;
this.orderid = orderid;
this.goodname = goodname;
this.goodcode = goodcode;
this.buynum = buynum;
this.codes = codes;
}
public long getid() {
return id;
}
public void setid(long id) {
this.id = id;
}
public long getorderid() {
return orderid;
}
public void setorderid(long orderid) {
this.orderid = orderid;
}
public string getgoodname() {
return goodname;
}
public void setgoodname(string goodname) {
this.goodname = goodname;
}
public string getgoodcode() {
return goodcode;
}
public void setgoodcode(string goodcode) {
this.goodcode = goodcode;
}
public integer getbuynum() {
return buynum;
}
public void setbuynum(integer buynum) {
this.buynum = buynum;
}
public list<codemodel> getcodes() {
return codes;
}
public void setcodes(list<codemodel> codes) {
this.codes = codes;
}
}
- codemodel :
package com.ph.excel.vo;
import com.ph.excel.annotation.iexcel;
/**
* @classname: codemodel
* @description: todo
* @date: 2023/6/2 14:31
* @author: ph9527
* @version: 1.0
*/
public class codemodel {
private long id;
private long detailid;
@iexcel(name = "追溯码",isnotblank = true)
private string code;
@override
public string tostring() {
return "codemodel{" +
"id=" + id +
", detailid=" + detailid +
", code='" + code + '\'' +
'}';
}
public codemodel() {
}
public codemodel(long id, long detailid, string code) {
this.id = id;
this.detailid = detailid;
this.code = code;
}
public long getid() {
return id;
}
public void setid(long id) {
this.id = id;
}
public long getdetailid() {
return detailid;
}
public void setdetailid(long detailid) {
this.detailid = detailid;
}
public string getcode() {
return code;
}
public void setcode(string code) {
this.code = code;
}
}
b :测试读取excel
读取用到的excel为:

c :测试读取的代码
/**
* 读取excel测试
*
* @return
*/
@getmapping("/read")
public jsonobject readexcel() throws filenotfoundexception {
jsonobject rs = new jsonobject();
file file = new file("d:\\ph\\desktop\\test.xlsx");
fileinputstream inputstream = new fileinputstream(file);
//读取数据
list<ordermodel> orders = iexcelutils.readexcel(inputstream, file.getname(), ordermodel.class);
//获取因必填项没填写,未读取的数据信息
list<iexcelutils.blankrowinfo> errrowinfos = iexcelutils.geterrrowinfo();
rs.put("读取的数据orders", orders);
rs.put("错误的数据errrowinfo", errrowinfos);
return rs;
}
d :读取excel的结果:


e:测试导出excel
我导出测试的数据直接读取的exel,下面是读取的excel图

f : 导出测试代码:
/**
* 导出测试
* @param response
* @throws exception
*/
@getmapping("/export")
public void export(httpservletresponse response) throws exception {
//创建测试数据
list<ordermodel> data = createexportdata();
//导出excel
iexcelutils.exportexcel("订单", "订单信息", response, data, ordermodel.class);
}
g:导出结果:
因为对订单和明细排序了,所以"购买人"在excel第一列,"订单时间"在excel第二列

总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论