1、我这里使用的是Spring Boot开发的,创建工程自行创建
2、导入相关依赖
org.springframework.boot
spring-boot-starter-web
2.3.6.RELEASE
org.apache.poi
poi-ooxml
4.1.2
cn.hutool
hutool-all
5.3.8
mysql
mysql-connector-java
8.0.22
org.projectlombok
lombok
1.18.16
commons-io
commons-io
2.11.0
3、yml配置数据库
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://120.25.228.68:3306/ExcelTest?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
4、导入Excel相关工具类
如下四个
ExcelClassField
package com.geesun.utils;
import java.util.LinkedHashMap;
/**
* @author Mr.ZJW
* @date 2021/12/17
*/
public class ExcelClassField {
/** 字段名称 */
private String fieldName;
/** 表头名称 */
private String name;
/** 映射关系 */
private LinkedHashMap kvMap;
/** 示例值 */
private Object example;
/** 排序 */
private int sort;
/** 是否为注解字段:0-否,1-是 */
private int hasAnnotation;
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LinkedHashMap getKvMap() {
return kvMap;
}
public void setKvMap(LinkedHashMap kvMap) {
this.kvMap = kvMap;
}
public Object getExample() {
return example;
}
public void setExample(Object example) {
this.example = example;
}
public int getSort() {
return sort;
}
public void setSort(int sort) {
this.sort = sort;
}
public int getHasAnnotation() {
return hasAnnotation;
}
public void setHasAnnotation(int hasAnnotation) {
this.hasAnnotation = hasAnnotation;
}
}
ExcelExport
package com.geesun.utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Mr.ZJW
* @date 2021/12/17
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelExport {
/** 字段名称 */
String value();
/** 导出排序先后: 数字越小越靠前(默认按Java类字段顺序导出) */
int sort() default 0;
/** 导出映射,格式如:0-未知;1-男;2-女 */
String kv() default "";
/** 导出模板示例值(有值的话,直接取该值,不做映射) */
String example() default "";
}
ExcelImport
package com.geesun.utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Mr.ZJW
* @date 2021/12/17
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelImport {
/** 字段名称 */
String value();
/** 导出映射,格式如:0-未知;1-男;2-女 */
String kv() default "";
/** 是否为必填字段(默认为非必填) */
boolean required() default false;
/** 最大长度(默认255) */
int maxLength() default 255;
/** 导入唯一性验证(多个字段则取联合验证) */
boolean unique() default false;
}
ExcelUtils
package com.geesun.utils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URL;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFDataValidation;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
/**
* Excel导入导出工具类
* @author Mr.ZJW
* @date 2021/12/17
*/
public class ExcelUtils {
private static final String XLSX = ".xlsx";
private static final String XLS = ".xls";
public static final String ROW_MERGE = "row_merge";
public static final String COLUMN_MERGE = "column_merge";
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final String ROW_NUM = "rowNum";
private static final String ROW_DATA = "rowData";
private static final String ROW_TIPS = "rowTips";
private static final int CELL_OTHER = 0;
private static final int CELL_ROW_MERGE = 1;
private static final int CELL_COLUMN_MERGE = 2;
private static final int IMG_HEIGHT = 30;
private static final int IMG_WIDTH = 30;
private static final char LEAN_LINE = '/';
private static final int BYTES_DEFAULT_LENGTH = 10240;
private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance();
public static List readFile(File file, Class clazz) throws Exception {
JSONArray array = readFile(file);
return getBeanList(array, clazz);
}
public static List readMultipartFile(MultipartFile mFile, Class clazz) throws Exception {
JSONArray array = readMultipartFile(mFile);
return getBeanList(array, clazz);
}
public static JSONArray readFile(File file) throws Exception {
return readExcel(null, file);
}
public static JSONArray readMultipartFile(MultipartFile mFile) throws Exception {
return readExcel(mFile, null);
}
private static List getBeanList(JSONArray array, Class clazz) throws Exception {
List list = new ArrayList<>();
Map uniqueMap = new HashMap<>(16);
for (int i = 0; i < array.size(); i++) {
list.add(getBean(clazz, array.getJSONObject(i), uniqueMap));
}
return list;
}
/**
* 获取每个对象的数据
*/
private static T getBean(Class c, JSONObject obj, Map uniqueMap) throws Exception {
T t = c.newInstance();
Field[] fields = c.getDeclaredFields();
List errMsgList = new ArrayList<>();
boolean hasRowTipsField = false;
StringBuilder uniqueBuilder = new StringBuilder();
int rowNum = 0;
for (Field field : fields) {
// 行号
if (field.getName().equals(ROW_NUM)) {
rowNum = obj.getInteger(ROW_NUM);
field.setAccessible(true);
field.set(t, rowNum);
continue;
}
// 是否需要设置异常信息
if (field.getName().equals(ROW_TIPS)) {
hasRowTipsField = true;
continue;
}
// 原始数据
if (field.getName().equals(ROW_DATA)) {
field.setAccessible(true);
field.set(t, obj.toString());
continue;
}
// 设置对应属性值
setFieldValue(t,field, obj, uniqueBuilder, errMsgList);
}
// 数据唯一性校验
if (uniqueBuilder.length() > 0) {
if (uniqueMap.containsValue(uniqueBuilder.toString())) {
Set rowNumKeys = uniqueMap.keySet();
for (Integer num : rowNumKeys) {
if (uniqueMap.get(num).equals(uniqueBuilder.toString())) {
errMsgList.add(String.format("数据唯一性校验失败,(%s)与第%s行重复)", uniqueBuilder, num));
}
}
} else {
uniqueMap.put(rowNum, uniqueBuilder.toString());
}
}
// 失败处理
if (errMsgList.isEmpty() && !hasRowTipsField) {
return t;
}
StringBuilder sb = new StringBuilder();
int size = errMsgList.size();
for (int i = 0; i < size; i++) {
if (i == size - 1) {
sb.append(errMsgList.get(i));
} else {
sb.append(errMsgList.get(i)).append(";");
}
}
// 设置错误信息
for (Field field : fields) {
if (field.getName().equals(ROW_TIPS)) {
field.setAccessible(true);
field.set(t, sb.toString());
}
}
return t;
}
private static void setFieldValue(T t, Field field, JSONObject obj, StringBuilder uniqueBuilder, List errMsgList) {
// 获取 ExcelImport 注解属性
ExcelImport annotation = field.getAnnotation(ExcelImport.class);
if (annotation == null) {
return;
}
String cname = annotation.value();
if (cname.trim().length() == 0) {
return;
}
// 获取具体值
String val = null;
if (obj.containsKey(cname)) {
val = getString(obj.getString(cname));
}
if (val == null) {
return;
}
field.setAccessible(true);
// 判断是否必填
boolean require = annotation.required();
if (require && val.isEmpty()) {
errMsgList.add(String.format("[%s]不能为空", cname));
return;
}
// 数据唯一性获取
boolean unique = annotation.unique();
if (unique) {
if (uniqueBuilder.length() > 0) {
uniqueBuilder.append("--").append(val);
} else {
uniqueBuilder.append(val);
}
}
// 判断是否超过最大长度
int maxLength = annotation.maxLength();
if (maxLength > 0 && val.length() > maxLength) {
errMsgList.add(String.format("[%s]长度不能超过%s个字符(当前%s个字符)", cname, maxLength, val.length()));
}
// 判断当前属性是否有映射关系
LinkedHashMap kvMap = getKvMap(annotation.kv());
if (!kvMap.isEmpty()) {
boolean isMatch = false;
for (String key : kvMap.keySet()) {
if (kvMap.get(key).equals(val)) {
val = key;
isMatch = true;
break;
}
}
if (!isMatch) {
errMsgList.add(String.format("[%s]的值不正确(当前值为%s)", cname, val));
return;
}
}
// 其余情况根据类型赋值
String fieldClassName = field.getType().getSimpleName();
try {
if ("String".equalsIgnoreCase(fieldClassName)) {
field.set(t, val);
} else if ("boolean".equalsIgnoreCase(fieldClassName)) {
field.set(t, Boolean.valueOf(val));
} else if ("int".equalsIgnoreCase(fieldClassName) || "Integer".equals(fieldClassName)) {
try {
field.set(t, Integer.valueOf(val));
} catch (NumberFormatException e) {
errMsgList.add(String.format("[%s]的值格式不正确(当前值为%s)", cname, val));
}
} else if ("double".equalsIgnoreCase(fieldClassName)) {
field.set(t, Double.valueOf(val));
} else if ("long".equalsIgnoreCase(fieldClassName)) {
field.set(t, Long.valueOf(val));
} else if ("BigDecimal".equalsIgnoreCase(fieldClassName)) {
field.set(t, new BigDecimal(val));
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static JSONArray readExcel(MultipartFile mFile, File file) throws IOException {
boolean fileNotExist = (file == null || !file.exists());
if (mFile == null && fileNotExist) {
return new JSONArray();
}
// 解析表格数据
InputStream in;
String fileName;
if (mFile != null) {
// 上传文件解析
in = mFile.getInputStream();
fileName = getString(mFile.getOriginalFilename()).toLowerCase();
} else {
// 本地文件解析
in = new FileInputStream(file);
fileName = file.getName().toLowerCase();
}
Workbook book;
if (fileName.endsWith(XLSX)) {
book = new XSSFWorkbook(in);
} else if (fileName.endsWith(XLS)) {
POIFSFileSystem poifsFileSystem = new POIFSFileSystem(in);
book = new HSSFWorkbook(poifsFileSystem);
} else {
return new JSONArray();
}
JSONArray array = read(book);
book.close();
in.close();
return array;
}
private static JSONArray read(Workbook book) {
// 获取 Excel 文件第一个 Sheet 页面
Sheet sheet = book.getSheetAt(0);
return readSheet(sheet);
}
private static JSONArray readSheet(Sheet sheet) {
// 首行下标
int rowStart = sheet.getFirstRowNum();
// 尾行下标
int rowEnd = sheet.getLastRowNum();
// 获取表头行
Row headRow = sheet.getRow(rowStart);
if (headRow == null) {
return new JSONArray();
}
int cellStart = headRow.getFirstCellNum();
int cellEnd = headRow.getLastCellNum();
Map keyMap = new HashMap<>();
for (int j = cellStart; j < cellEnd; j++) {
// 获取表头数据
String val = getCellValue(headRow.getCell(j));
if (val != null && val.trim().length() != 0) {
keyMap.put(j, val);
}
}
// 如果表头没有数据则不进行解析
if (keyMap.isEmpty()) {
return (JSONArray) Collections.emptyList();
}
// 获取每行JSON对象的值
JSONArray array = new JSONArray();
// 如果首行与尾行相同,表明只有一行,返回表头数据
if (rowStart == rowEnd) {
JSONObject obj = new JSONObject();
// 添加行号
obj.put(ROW_NUM, 1);
for (int i : keyMap.keySet()) {
obj.put(keyMap.get(i), "");
}
array.add(obj);
return array;
}
for (int i = rowStart + 1; i <= rowEnd; i++) {
Row eachRow = sheet.getRow(i);
JSONObject obj = new JSONObject();
// 添加行号
obj.put(ROW_NUM, i + 1);
StringBuilder sb = new StringBuilder();
for (int k = cellStart; k < cellEnd; k++) {
if (eachRow != null) {
String val = getCellValue(eachRow.getCell(k));
// 所有数据添加到里面,用于判断该行是否为空
sb.append(val);
obj.put(keyMap.get(k), val);
}
}
if (sb.length() > 0) {
array.add(obj);
}
}
return array;
}
private static String getCellValue(Cell cell) {
// 空白或空
if (cell == null || cell.getCellTypeEnum() == CellType.BLANK) {
return "";
}
// String类型
if (cell.getCellTypeEnum() == CellType.STRING) {
String val = cell.getStringCellValue();
if (val == null || val.trim().length() == 0) {
return "";
}
return val.trim();
}
// 数字类型
if (cell.getCellTypeEnum() == CellType.NUMERIC) {
// 科学计数法类型
return NUMBER_FORMAT.format(cell.getNumericCellValue()) + "";
}
// 布尔值类型
if (cell.getCellTypeEnum() == CellType.BOOLEAN) {
return cell.getBooleanCellValue() + "";
}
// 错误类型
return cell.getCellFormula();
}
public static void exportTemplate(HttpServletResponse response, String fileName, Class clazz) {
exportTemplate(response, fileName, fileName, clazz, false);
}
public static void exportTemplate(HttpServletResponse response, String fileName, String sheetName,
Class clazz) {
exportTemplate(response, fileName, sheetName, clazz, false);
}
public static void exportTemplate(HttpServletResponse response, String fileName, Class clazz,
boolean isContainExample) {
exportTemplate(response, fileName, fileName, clazz, isContainExample);
}
public static void exportTemplate(HttpServletResponse response, String fileName, String sheetName,
Class clazz, boolean isContainExample) {
// 获取表头字段
List headFieldList = getExcelClassFieldList(clazz);
// 获取表头数据和示例数据
List> sheetDataList = new ArrayList<>();
List
6、User类,对应Excel表
package com.geesun.pojo.entity;
import com.geesun.utils.ExcelImport;
import lombok.Data;
/**
* Description:
* @Author: Mr.ZJW
* DateTime: 2022/2/23 11:23
*/
@Data
public class User {
@ExcelImport("姓名")
private String name;
@ExcelImport("年龄")
private Integer age;
@ExcelImport("性别")
private String sex;
@ExcelImport("电话")
private String phone;
@ExcelImport("城市")
private String city;
}
1、Controller
@RestController
@RequestMapping("/excel")
public class ExcelController {
/**
* excel数据导入
* @param file
* @return
* @throws Exception
*/
@PostMapping("/excelImport")
public void excelImport(@RequestPart("excelImport")MultipartFile file) throws Exception {
List users = ExcelUtils.readMultipartFile(file, User.class);
users.forEach(System.out::println);
}
}
测试结果:
3、导入解析为对象(字段自动映射)
对于有的枚举数据,通常我们导入的时候,表格中的数据是值,而在数据保存时,往往用的是键,比如:我们用sex=1可以表示为男,sex=2表示为女,那么我们通过配置也可以达到导入时,数据的自动映射。
那么,我们只需要将Java实体中的对象sex字段的类型改为对应的数字类型Integer,然后再注解中配置好 kv 属性(属性格式为:键1-值1;键2-值2;键3-值3;…)
测试效果:
4、获取Excel的行号:
在User类上添加多一个属性
测试效果:
5、获取原始数据:
在做页面数据导入的时候,如果某行存在错误,一般我们会将原始的数据拿出来分析,为什么会造成数据错误。那么,我们在实体类中,增加一个 String 类型的 rowData 字段即可。
测试效果:
6、获取错误提示
当我们在导入数据的时候,如果某行数据存在,字段类型不正确,长度超过最大限制(详见1.2.7),必填字段验证(1.2.8),数据唯一性验证(1.2.9)等一些错误时候,我们可以往对象中添加一个 String 类型的 rowTips 字段,则可以直接拿到对应的错误信息。
比如,我们将表格中李四的性别改为F(F并不是映射数据),将王五的年龄改为二十(不能转换为Integer类型数据)。
测试效果:可以看到,我们可以通过 rowTips 直接拿到对应的错误数据提示。
7、限制字段长度
比如,我们手机通常为11为长度,那么不妨限制电话的最大长度位数为11位。
对应的做法,就是在 @ExcelImport 注解中,设置 maxLength = 11 即可。
比如,我们将张三的电话长度设置为超过11位数的一个字符串
测试效果:
8、必填字段验证
我们在做数据导入的时候,往往还会有一些必填字段,比如用户的名称,电话。
那么,我们只需要在 @ExcelImport 注解属性中,加上 required = true 即可。
我们将张三的电话去除为空,进行测试。
测试效果:
9、数据唯一性验证
(1) 单字段唯一性验证
我们在导入数据的时候,某个字段是具有唯一性的,比如我们这里假设规定姓名不能重复,那么则可以在对应字段的 @ExcelImport 注解上加上 unique = true 属性。
如果你导入的数据存在多字段唯一性验证这种情况,只需要将每个对应字段的 @ExcelImport 注解属性中,都加上 required = true 即可。
比如:我们将姓名和电话两个字段进行联合唯一性验证(即不能存在有名称和电话都一样的数据,单个字段属性重复允许)。
首先,我们将刚刚的Excel的数据进行导入。
测试效果:可以看到,虽然名称有相同,但电话不相同,所以这里并没有提示唯一性验证错误。
现在,我们将最后一行的电话也改为和第1行一样的,于是,现在就存在了违背唯一性的两条数据。
测试效果:可以看到,我们的联合唯一性验证生效了。:
1、我们在做数据导入的时候,往往首先会提供一个模版供其下载,这样用户在导入的时候才知道如何去填写数据。导出模板除了可以用上面的动态导出,这里还提供了一种更加便捷的写法。只需要创建一个类,然后再对应字段上打上 @ExcelExport 注解类即可。
@Data
public class User {
@ExcelExport(value = "姓名")
private String name;
@ExcelExport("年龄")
private Integer age;
@ExcelExport(value = "性别")
private String sex;
@ExcelExport(value = "电话")
private String phone;
@ExcelExport("城市")
private String city;
}
Controller 代码:
/**
* 导出Excel表
* @param response
*/
@RequestMapping("/excelPort")
public void excelPort(HttpServletResponse response){
ExcelUtils.exportTemplate(response,"用户表",User.class);
}
测试效果:
2、导出模板(附示例数据)
我们在做模版下载时候,有时往往会携带一条样本数据,好提示用户数据格式是什么,那么我们只需要在对应字段上进行配置即可。
Controller代码:
测试效果:
3、按对象导出
我们还可以通过 List 对象,对数据直接进行导出。首先,同样需要在对应类的字段上,设置导出名称。
Controller 代码:
@RestController
@RequestMapping("/excel")
public class ExcelController {
/**
* 导出Excel表
* @param response
*/
@RequestMapping("/excelPort")
public void excelPort(HttpServletResponse response){
//创建ArrayList
List userList = new ArrayList<>();
//实例用户对象
User user1 = new User();
//设置用户1信息
user1.setName("张三");
user1.setAge(20);
user1.setSex("男");
user1.setPhone("110");
user1.setCity("北京");
User user2 = new User();
//设置用户2信息
user2.setName("李四");
user2.setAge(30);
user2.setSex("女");
user2.setPhone("120");
user2.setCity("上海");
//添加到List
userList.add(user1);
userList.add(user2);
ExcelUtils.export(response,"用户表",userList,User.class);
}
}
测试效果:
4、从数据库获取数据导出
4.1、sql表结构
4.2、实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "`User`")
public class User implements Serializable {
/**
* 用户id
*/
@TableId(value = "id", type = IdType.INPUT)
@ExcelExport(value = "用户id")
private Long id;
/**
* 用户名
*/
@TableField(value = "name")
@ExcelExport(value = "用户名")
private String name;
/**
* 性别
*/
@TableField(value = "sex")
@ExcelExport(value = "性别")
private String sex;
/**
* 年龄
*/
@TableField(value = "age")
@ExcelExport(value = "年龄")
private Long age;
/**
* 城市
*/
@TableField(value = "city")
@ExcelExport(value = "城市")
private String city;
/**
* 手机号
*/
@TableField(value = "phone")
@ExcelExport(value = "手机号")
private String phone;
private static final long serialVersionUID = 1L;
}
4.3Controller代码:
@RequestMapping("/excel")
@RestController
public class ExcelPortController {
@Autowired
private UserService userService;
@RequestMapping("/excelPort")
public void excelPort(HttpServletResponse response){
List list = userService.list();
ExcelUtils.export(response,"用户信息表",list,User.class);
}
}
4.5测试结果:
访问然后会跳出一:
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦