Apache POI 是一个 Java 实现的操作 Office 文档的工具库,通常用于处理 Excel 文件,同时它也支持读写 MS Word 和 MS PowerPoint 文件。
废话不多说,这里记录下如何使用 POI 在 Web 程序中进行 包含图片的 Excel 的导入导出,以及一个整合了Excel导入导出的学生信息采集系统示例。
Maven依赖如下:
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>4.1.0version>
dependency>
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFPatriarch;
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.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
public class PoiExcelExport {
public static void main(String[] args) throws IOException {
exportXlsx();
// exportXls();
}
public static void exportXlsx() throws IOException {
List<String> titleList = Arrays.asList("姓名", "证件照");
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("学生信息");
// 画图的顶级管理器,一个sheet只能获取一个(一定要注意这点)
XSSFDrawing patriarch = sheet.createDrawingPatriarch();
XSSFRow row = sheet.createRow(0);
row.setHeight((short) 650);
XSSFCellStyle headStyle = workbook.createCellStyle();
headStyle.setAlignment(HorizontalAlignment.CENTER);
headStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//声明列对象
XSSFCell cell;
//创建标题
for (int i = 0; i < titleList.size(); i++) {
sheet.setColumnWidth(i, 6000);
cell = row.createCell(i);
cell.setCellValue(titleList.get(i));
XSSFFont font = workbook.createFont();
//设置excel数据字体颜色
font.setColor(Font.COLOR_NORMAL);
//设置excel数据字体大小
font.setFontHeightInPoints((short) 15);
headStyle.setFont(font);
cell.setCellStyle(headStyle);
}
XSSFCellStyle dataStyle = workbook.createCellStyle();
dataStyle.setAlignment(HorizontalAlignment.CENTER);
dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//自动换行
dataStyle.setWrapText(true);
//填充数据
row = sheet.createRow(1);
row.setHeight((short) 2000);
// ----- 姓名
cell = row.createCell(0);
cell.setCellValue("蒙奇·D·路飞");
// ----- 证件照
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
BufferedImage image = ImageIO.read(new URL("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1400063385,2378282079&fm=26&gp=0.jpg"));
ImageIO.write(image, "jpg", byteArrayOut);
XSSFClientAnchor anchor = new XSSFClientAnchor(
0, 0, 0, 0,
(short) 1, 1, (short) 2, 2
);
// 插入图片
patriarch.createPicture(
anchor,
workbook.addPicture(
byteArrayOut.toByteArray(),
XSSFWorkbook.PICTURE_TYPE_JPEG
)
);
byteArrayOut.close();
//输出至文件
workbook.write(new FileOutputStream(new File("D:\\测试.xlsx")));
}
public static void exportXls() throws IOException {
List<String> titleList = Arrays.asList("姓名", "证件照");
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("学生信息");
// 画图的顶级管理器,一个sheet只能获取一个(一定要注意这点)
HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
HSSFRow row = sheet.createRow(0);
row.setHeight((short) 650);
HSSFCellStyle headStyle = workbook.createCellStyle();
headStyle.setAlignment(HorizontalAlignment.CENTER);
headStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//声明列对象
HSSFCell cell;
//创建标题
for (int i = 0; i < titleList.size(); i++) {
sheet.setColumnWidth(i, 6000);
cell = row.createCell(i);
cell.setCellValue(titleList.get(i));
HSSFFont font = workbook.createFont();
//设置excel数据字体颜色
font.setColor(Font.COLOR_NORMAL);
//设置excel数据字体大小
font.setFontHeightInPoints((short) 15);
headStyle.setFont(font);
cell.setCellStyle(headStyle);
}
HSSFCellStyle dataStyle = workbook.createCellStyle();
dataStyle.setAlignment(HorizontalAlignment.CENTER);
dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//自动换行
dataStyle.setWrapText(true);
//填充数据
row = sheet.createRow(1);
row.setHeight((short) 2000);
// ----- 姓名
cell = row.createCell(0);
cell.setCellValue("蒙奇·D·路飞");
// ----- 证件照
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
BufferedImage image = ImageIO.read(new URL("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2778527992,3607430593&fm=26&gp=0.jpg"));
ImageIO.write(image, "jpg", byteArrayOut);
HSSFClientAnchor anchor = new HSSFClientAnchor(
0, 0, 0, 0,
(short) 1, 1, (short) 2, 2
);
// 插入图片
patriarch.createPicture(
anchor,
workbook.addPicture(
byteArrayOut.toByteArray(),
HSSFWorkbook.PICTURE_TYPE_JPEG
)
);
byteArrayOut.close();
//输出至文件
workbook.write(new FileOutputStream(new File("D:\\测试.xls")));
}
}
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFPicture;
import org.apache.poi.hssf.usermodel.HSSFShape;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.PictureData;
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.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFPicture;
import org.apache.poi.xssf.usermodel.XSSFShape;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PoiExcelImport {
public static void main(String[] args) throws IOException {
String filePath = "D:\\excel\\导入.xlsx";
String picStorePath = "D:\\excel\\";
File file = new File(filePath);
String fileName = file.getName();
FileInputStream fileInputStream = new FileInputStream(file);
importExcelWithImage(fileInputStream, fileName, picStorePath);
}
public static void importExcelWithImage(InputStream in, String fileName, String picStorePath) throws IOException {
Workbook wookbook;
Sheet sheet;
//图片数据
Map<String, PictureData> pictureDataMap = null;
//得到工作簿
try {
if (fileName.endsWith(".xls")) {
//2003版本的excel,用.xls结尾
wookbook = new HSSFWorkbook(in);
} else {
//2007版本的excel,用.xlsx结尾
wookbook = new XSSFWorkbook(in);
}
} catch (Exception e) {
e.printStackTrace();
return;
}
// ----- 获取图片数据 -----
sheet = wookbook.getSheetAt(0);
// 判断用07还是03的方法获取图片
if (fileName.endsWith(".xls")) {
pictureDataMap = getXlsPictures((HSSFSheet) sheet);
} else {
pictureDataMap = getXlsxPictures((XSSFSheet) sheet);
}
// ----- 获得标题 -----
Row rowHead = sheet.getRow(0);
int rowHeadPhysicalNumberOfCells = rowHead.getPhysicalNumberOfCells();
for (int i = 0; i < rowHeadPhysicalNumberOfCells; i++) {
Cell cell = rowHead.getCell(i);
System.out.println(cell.getStringCellValue());
}
// ----- 获取其他数据 -----
int totalRowNum = sheet.getLastRowNum();
for (int i = 1; i <= totalRowNum; i++) {
Row row = sheet.getRow(i);
int physicalNumberOfCells = row.getPhysicalNumberOfCells();
for (int j = 0; j < physicalNumberOfCells; j++) {
Cell cell = row.getCell(j);
System.out.println(cell.getStringCellValue());
}
}
// ----- 输出图片 -----
try {
printImg(pictureDataMap, picStorePath);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取图片和位置 (xls)
*
* @param sheet
* @return
* @throws IOException
*/
public static Map<String, PictureData> getXlsPictures(HSSFSheet sheet) throws IOException {
Map<String, PictureData> map = new HashMap<>();
List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();
for (HSSFShape shape : list) {
if (shape instanceof HSSFPicture) {
HSSFPicture picture = (HSSFPicture) shape;
HSSFClientAnchor cAnchor = (HSSFClientAnchor) picture.getAnchor();
PictureData pdata = picture.getPictureData();
// 行号-列号
String key = cAnchor.getRow1() + "-" + cAnchor.getCol1();
map.put(key, pdata);
}
}
return map;
}
/**
* 获取图片和位置 (xlsx)
*
* @param sheet
* @return
* @throws IOException
*/
public static Map<String, PictureData> getXlsxPictures(XSSFSheet sheet) throws IOException {
Map<String, PictureData> map = new HashMap<>();
List<POIXMLDocumentPart> list = sheet.getRelations();
for (POIXMLDocumentPart part : list) {
if (part instanceof XSSFDrawing) {
XSSFDrawing drawing = (XSSFDrawing) part;
List<XSSFShape> shapes = drawing.getShapes();
for (XSSFShape shape : shapes) {
XSSFPicture picture = (XSSFPicture) shape;
XSSFClientAnchor anchor = picture.getPreferredSize();
CTMarker marker = anchor.getFrom();
String key = marker.getRow() + "-" + marker.getCol();
map.put(key, picture.getPictureData());
}
}
}
return map;
}
public static void printImg(Map<String, PictureData> sheetList, String path) throws IOException {
Object[] key = sheetList.keySet().toArray();
for (int i = 0; i < sheetList.size(); i++) {
// 获取图片流
PictureData pic = sheetList.get(key[i]);
// 获取图片索引
String picName = key[i].toString();
// 获取图片格式
String ext = pic.suggestFileExtension();
byte[] data = pic.getData();
//图片保存路径
String imgPath = path + picName + "." + ext;
FileOutputStream out = new FileOutputStream(imgPath);
System.out.println(imgPath);
out.write(data);
out.close();
}
}
}
新建一个Maven 项目,在 pom 文件中引入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.flywaydbgroupId>
<artifactId>flyway-coreartifactId>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
<version>4.2.0version>
dependency>
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
<scope>runtimescope>
dependency>
这里不介绍这两个依赖的具体用法,整合是为了项目快速构建及初始化,减少第三方服务依赖,比如 MySQL 环境。
在 resource 目录下创建目录 db.migration
,在该目录下新建文件 V1__init.sql
,该文件用于数据库初始化,这里用于创建学生信息表和导入初始数据。
文件内容如下:
CREATE TABLE student_info
(
student_number varchar(255) NOT NULL comment '学号',
name varchar(255) DEFAULT NULL comment '姓名',
age int(3) DEFAULT NULL comment '年龄',
grade varchar(255) DEFAULT NULL comment '年级',
classroom varchar(255) DEFAULT NULL comment '班级',
photo_url varchar(255) DEFAULT NULL comment '照片',
PRIMARY KEY (`student_number`)
);
insert into student_info (student_number, name, age, grade, classroom, photo_url)
values ('001', '埼玉', 25, '九年级', '终极一班', 'https://i.loli.net/2019/09/22/HaWN8jXVuPGwdri.jpg'),
('002', '蒙奇·D·路飞', 19, '七年级', '终极一班', 'https://i.loli.net/2019/09/22/ZaIMgPrxstYk4ze.jpg'),
('003', '漩涡鸣人', 19, '七年级', '终极一班', 'https://i.loli.net/2019/09/22/u2GvA7CHp35qWlE.jpg')
;
标识 Excel 读取列的注解:
ExcelProperty :
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标记excel要读取的属性
*
* @author 19848
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelProperty {
String[] value() default {""};
int index() default -1;
}
ExcelImageData:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标识Excel图片数据
*
* @author 19848
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelImageData {
}
学生信息 Excel 数据实体:
@Data
public class StudentInfoExcelDto {
@ExcelProperty(value = "学号", index = 1)
private String studentNumber;
@ExcelProperty(value = "姓名", index = 2)
private String name;
@ExcelProperty(value = "年龄", index = 3)
private Integer age;
@ExcelProperty(value = "年纪", index = 4)
private String grade;
@ExcelProperty(value = "班级", index = 5)
private String classroom;
@ExcelImageData
@ExcelProperty(value = "照片", index = 6)
private String photoUrl;
}
POI 业务类:
import com.example.poi.annotation.ExcelImageData;
import com.example.poi.annotation.ExcelProperty;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFPicture;
import org.apache.poi.hssf.usermodel.HSSFShape;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@Service
public class PoiService {
/**
* 创建 2007 以上版本工作簿
*
* @param sheetName
* @param dataList
* @param clazz
* @return
*/
public XSSFWorkbook createXSSFWorkbook(String sheetName, List<?> dataList, Class<?> clazz) {
short headRowHeight = (short) 1000;
short headRowFontHeight = (short) 15;
int columnWidth = 6000;
short dataRowHeight = (short) 1500;
// ----- 获取标题 -----
List<String> titleList = new LinkedList<>();
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
ExcelProperty annotation = declaredField.getAnnotation(ExcelProperty.class);
String value = annotation.value()[0];
titleList.add(value);
}
// ----- 工作簿 -----
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet(sheetName);
// 画图的顶级管理器,一个sheet只能获取一个(一定要注意这点)
XSSFDrawing patriarch = sheet.createDrawingPatriarch();
XSSFRow row = sheet.createRow(0);
row.setHeight(headRowHeight);
// ----- 标题样式 -----
XSSFCellStyle headStyle = workbook.createCellStyle();
headStyle.setAlignment(HorizontalAlignment.CENTER);
headStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//单元格背景
headStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
//单元格边框
headStyle.setBorderBottom(BorderStyle.MEDIUM);
headStyle.setBorderLeft(BorderStyle.MEDIUM);
headStyle.setBorderTop(BorderStyle.MEDIUM);
headStyle.setBorderRight(BorderStyle.MEDIUM);
XSSFFont font = workbook.createFont();
//设置excel数据字体颜色
font.setColor(Font.COLOR_NORMAL);
//设置excel数据字体大小
font.setFontHeightInPoints(headRowFontHeight);
headStyle.setFont(font);
//自动换行
headStyle.setWrapText(true);
// ----- 组装标题行 -----
//声明列对象
XSSFCell cell;
//创建标题
for (int i = 0; i < titleList.size(); i++) {
sheet.setColumnWidth(i, columnWidth);
cell = row.createCell(i);
cell.setCellValue(titleList.get(i));
cell.setCellStyle(headStyle);
}
// ----- 数据样式样式 -----
XSSFCellStyle dataStyle = workbook.createCellStyle();
dataStyle.setAlignment(HorizontalAlignment.CENTER);
dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//自动换行
dataStyle.setWrapText(true);
// ----- 组装数据行 -----
for (int i = 0; i < dataList.size(); i++) {
row = sheet.createRow(i + 1);
row.setHeight(dataRowHeight);
Object data = dataList.get(i);
for (Field declaredField : declaredFields) {
Object o = null;
try {
declaredField.setAccessible(true);
o = declaredField.get(data);
if (o == null) {
continue;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
ExcelProperty excelProperty = declaredField.getAnnotation(ExcelProperty.class);
int index = excelProperty.index();
//普通数据
if (!declaredField.isAnnotationPresent(ExcelImageData.class)) {
cell = row.createCell(index - 1);
cell.setCellValue(String.valueOf(o));
cell.setCellStyle(dataStyle);
}
//图片数据
else {
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
BufferedImage image;
try {
String imageUrl = String.valueOf(o);
image = ImageIO.read(new URL(imageUrl));
ImageIO.write(image, "jpg", byteArrayOut);
//图片定位
XSSFClientAnchor anchor = new XSSFClientAnchor(
0, 0, columnWidth, dataRowHeight,
(short) index - 1, i + 1, (short) index, i + 2
);
// 插入图片
patriarch.createPicture(
anchor,
workbook.addPicture(
byteArrayOut.toByteArray(),
XSSFWorkbook.PICTURE_TYPE_JPEG
)
);
byteArrayOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return workbook;
}
/**
* 获取图片和位置 (xls)
*
* @param sheet
* @return
* @throws IOException
*/
public static Map<String, PictureData> getXlsPictures(HSSFSheet sheet) throws IOException {
Map<String, PictureData> map = new HashMap<>();
List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();
for (HSSFShape shape : list) {
if (shape instanceof HSSFPicture) {
HSSFPicture picture = (HSSFPicture) shape;
HSSFClientAnchor cAnchor = (HSSFClientAnchor) picture.getAnchor();
PictureData pdata = picture.getPictureData();
// 行号-列号
String key = cAnchor.getRow1() + "-" + cAnchor.getCol1();
map.put(key, pdata);
}
}
return map;
}
/**
* 获取图片和位置 (xlsx)
*
* @param sheet
* @return
* @throws IOException
*/
public static Map<String, PictureData> getXlsxPictures(XSSFSheet sheet) throws IOException {
Map<String, PictureData> map = new HashMap<>();
List<POIXMLDocumentPart> list = sheet.getRelations();
for (POIXMLDocumentPart part : list) {
if (part instanceof XSSFDrawing) {
XSSFDrawing drawing = (XSSFDrawing) part;
List<XSSFShape> shapes = drawing.getShapes();
for (XSSFShape shape : shapes) {
XSSFPicture picture = (XSSFPicture) shape;
XSSFClientAnchor anchor = picture.getPreferredSize();
CTMarker marker = anchor.getFrom();
// 行号-列号
String key = marker.getRow() + "-" + marker.getCol();
map.put(key, picture.getPictureData());
}
}
}
return map;
}
public static Object getCellValue(Cell cell) {
if (cell.getCellType() == CellType.BOOLEAN) {
return cell.getBooleanCellValue();
} else if (cell.getCellType() == CellType.NUMERIC) {
double numericCellValue = cell.getNumericCellValue();
return Double.valueOf(numericCellValue).intValue();
} else {
return cell.getStringCellValue();
}
}
}
这里使用 sm.ms 的图床服务,官方地址:https://sm.ms/。选择的原因主要是免费,并提供免登陆验证的图片上传API。
配置 RestTemplate Bean:
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.TimeUnit;
@Slf4j
@Configuration
public class BeanConfig {
@Bean
public RestTemplate restTemplate() {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS);
return new RestTemplate(new OkHttp3ClientHttpRequestFactory(builder.build()));
}
}
抽象 sm.ms 图片上传服务:
响应 DTO 如下:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* sm.ms网站图片上传响应dto
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class SmMsUploadResponseDto {
private Boolean success;
private String code;
private String message;
@JsonProperty("data")
private ResponseData data;
@JsonProperty("RequestId")
private String requestId;
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class ResponseData {
private Integer width;
private Integer height;
@JsonProperty("filename")
private String fileName;
@JsonProperty("storename")
private String storeName;
private Integer size;
private String path;
private String hash;
private String url;
private String delete;
private String page;
}
}
服务类如下:
import com.example.poi.dto.SmMsUploadResponseDto;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.UUID;
@Service
public class SmMsImageService {
/**
* 这里上传图片使用 https://sm.ms 网站的 API
*/
private static final String IMAGE_UPLOAD_URL = "https://sm.ms/api/v2/upload";
private static ObjectMapper MAPPER = new ObjectMapper();
@Autowired
private RestTemplate restTemplate;
public String upload(byte[] imageData) {
//请求头
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("multipart/form-data");
headers.setContentType(type);
headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36");
//图片参数
ByteArrayResource resource = new ByteArrayResource(imageData){
@Override
public String getFilename() {
return UUID.randomUUID().toString() + ".jpg";
}
};
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("smfile", resource);
//用HttpEntity封装整个请求报文
HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers);
String result = restTemplate.postForObject(IMAGE_UPLOAD_URL, files, String.class);
System.out.println(result);
try {
SmMsUploadResponseDto smMsUploadResponseDto = MAPPER.readValue(result, SmMsUploadResponseDto.class);
if (smMsUploadResponseDto.getSuccess()) {
return smMsUploadResponseDto.getData().getUrl();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
model:
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Entity
@Table(name = "student_info")
public class StudentInfo {
@Id
@Column(name = "student_number")
private String studentNumber;
@Column(name = "name")
private String name;
@Column(name = "age")
private Integer age;
@Column(name = "grade")
private String grade;
@Column(name = "classroom")
private String classroom;
@Column(name = "photo_url")
private String photoUrl;
}
repository:
import com.example.poi.model.StudentInfo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentInfoRepository extends JpaRepository<StudentInfo, String> {
}
service:
import com.example.poi.annotation.ExcelImageData;
import com.example.poi.annotation.ExcelProperty;
import com.example.poi.dto.StudentInfoExcelDto;
import com.example.poi.model.StudentInfo;
import com.example.poi.repository.StudentInfoRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class StudentInfoService {
@Autowired
private StudentInfoRepository studentInfoRepository;
@Autowired
private PoiService poiService;
@Autowired
private SmMsImageService smMsImageService;
public List<StudentInfo> selectList() {
return studentInfoRepository.findAll();
}
/**
* 导入学生信息(包含图片)
*
* @param file
*/
public void importStudentInfoExcel(MultipartFile file) throws IOException, IllegalAccessException {
String originalFilename = file.getOriginalFilename();
// ----- 校验excel文件 -----
// 这里省略校验逻辑...
// ----- 获取工作簿及图片数据 -----
InputStream inputStream;
Workbook wookbook = null;
Sheet sheet;
// ----- 获取excel中图片数据 -----
Map<String, PictureData> pictureDataMap = null;
inputStream = file.getInputStream();
if (originalFilename.endsWith(".xls")) {
//2003版本的excel,用.xls结尾
wookbook = new HSSFWorkbook(inputStream);
sheet = wookbook.getSheetAt(0);
pictureDataMap = PoiService.getXlsPictures((HSSFSheet) sheet);
} else {
//2007版本的excel,用.xlsx结尾
wookbook = new XSSFWorkbook(inputStream);
sheet = wookbook.getSheetAt(0);
pictureDataMap = PoiService.getXlsxPictures((XSSFSheet) sheet);
}
// ----- 上传图片,获取图片链接 -----
Map<String, String> imageInfoMap = new HashMap<>(pictureDataMap.size());
for (Map.Entry<String, PictureData> pictureDataEntry : pictureDataMap.entrySet()) {
String key = pictureDataEntry.getKey();
PictureData value = pictureDataEntry.getValue();
String imageUrl = smMsImageService.upload(value.getData());
imageInfoMap.put(key, imageUrl);
}
// ----- 获取导入数据 -----
List<StudentInfoExcelDto> studentInfoExcelDtos = new ArrayList<>();
Class<StudentInfoExcelDto> clazz = StudentInfoExcelDto.class;
Field[] declaredFields = clazz.getDeclaredFields();
//数据总行数
int totalRowNum = sheet.getLastRowNum();
for (int i = 1; i <= totalRowNum; i++) {
StudentInfoExcelDto excelBO = new StudentInfoExcelDto();
//当前行数据
Row row = sheet.getRow(i);
//封装对应列的数据
for (Field declaredField : declaredFields) {
ExcelProperty excelProperty = declaredField.getAnnotation(ExcelProperty.class);
//当前字段对应列索引
int index = excelProperty.index() - 1;
//普通数据
if (!declaredField.isAnnotationPresent(ExcelImageData.class)) {
declaredField.setAccessible(true);
declaredField.set(excelBO, PoiService.getCellValue(row.getCell(index)));
}
//图片数据
else {
String indexKey = i + "-" + index;
declaredField.setAccessible(true);
declaredField.set(excelBO, imageInfoMap.get(indexKey));
}
}
studentInfoExcelDtos.add(excelBO);
}
// ---- 转化数据 -----
List<StudentInfo> studentInfos = new ArrayList<>();
for (StudentInfoExcelDto studentInfoExcelDto : studentInfoExcelDtos) {
StudentInfo studentInfo = new StudentInfo();
BeanUtils.copyProperties(studentInfoExcelDto, studentInfo);
studentInfos.add(studentInfo);
}
// ----- 持久化数据 -----
studentInfoRepository.saveAll(studentInfos);
}
/**
* 导出学生信息(包含图片)
*
* @param response
*/
public void exportStudentInfoExcel(HttpServletResponse response) {
// ----- 获取导出的学生信息数据 -----
List<StudentInfo> studentInfos = studentInfoRepository.findAll();
// ----- 转换数据类型 -----
List<StudentInfoExcelDto> excelDtos = new ArrayList<>();
for (StudentInfo studentInfo : studentInfos) {
StudentInfoExcelDto excelDto = new StudentInfoExcelDto();
BeanUtils.copyProperties(studentInfo, excelDto);
excelDtos.add(excelDto);
}
// ----- 获取工作簿 -----
XSSFWorkbook workbook = poiService.createXSSFWorkbook("学生信息", excelDtos, StudentInfoExcelDto.class);
// ----- 响应到客户端 -----
try {
String exportFileName = "StudentInfoExportData.xlsx";
setExcelResponseHeader(response, exportFileName);
OutputStream os = response.getOutputStream();
workbook.write(os);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
System.err.println(e.getMessage());
}
}
public void setExcelResponseHeader(HttpServletResponse response, String fileName) {
try {
try {
fileName = new String(fileName.getBytes(), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.setCharacterEncoding("UTF-8");
response.setContentType("application/vnd.ms-excel");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
response.addHeader("Pargam", "no-cache");
response.addHeader("Cache-Control", "no-cache");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
controller:
import com.example.poi.model.StudentInfo;
import com.example.poi.service.StudentInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@RestController
public class SimpleController {
@Autowired
private StudentInfoService studentInfoService;
@GetMapping("/")
public String hello() {
return "Hello,World!";
}
@GetMapping("/list")
public List<StudentInfo> list() {
return studentInfoService.selectList();
}
@PostMapping("/import")
public String importStudentInfoExcel(@RequestParam("file") MultipartFile file) throws IOException, IllegalAccessException {
studentInfoService.importStudentInfoExcel(file);
return "success";
}
@GetMapping(value = "/export")
public void exportStudentInfoExcel(HttpServletResponse response) throws IOException {
studentInfoService.exportStudentInfoExcel(response);
}
}
启动项目:
以导出的Excel为模板,添加几个新的学生信息,作为学生信息导入的Excel文件:
重新查看下学生信息列表,导入成功:
注意,这里由于 sm.ms 对于单位时间内重复图片导入会有限制,可能会导入失败,更换图片即可。