一、單文件的上傳功能
?????????這里是一個demo的流程圖,下面按照這個流程圖做了一個簡單的實現(xiàn),有部分判斷沒有加上,實際操作中,可以根據(jù)自己的需求進行增加或者修改。并且此處還是在接受文件傳入后將文件進行了下載,保存到本地的操作,這個要按照具體情況具體分析,看需求是否要把上傳進來的文件進行數(shù)據(jù)備份,或者直接以流的形式讀進來,然后進行解析等邏輯操作后,最后關(guān)閉釋放流也可以,沒有說一定要上傳完文件就要下載下來。
controller層 (接受前端傳入的文件參數(shù),為單個文件)
@RequestMapping( "/salary/server/excelxz")
public class SalaryExcelOperatController {
@Autowired
private SalaryExcelOperatService salaryexcelOperatService;
@PostMapping("/upload")
public RespondDto uploadFile(@RequestParam("file") MultipartFile multipartFile) {
SalaryExcelOperatVo excelOperatVo =salaryexcelOperatService.uploadExcel(multipartFile);
return new RespondDto(excelOperatVo);
}
service層 (這里使用了easyExcel的方式實現(xiàn)了,一個Excel中包含多個sheet的讀取操作,但本質(zhì)和POI的思想是差不多的?)
@Service
@Slf4j
public class SalaryExcelOperatServiceImpl implements SalaryExcelOperatService {
@Resource
private SalaryExcelOperatMapper salaryexcelOperatMapper;
@Resource
private LoginMapper loginMapper;
/**
* 上傳 Excel 文件
* 1、接收文件,保存到本地;
* 2、獲取本地文件路徑調(diào)用 readExcel 讀成ArrayList;
*/
@Override
public SalaryExcelOperatVo uploadExcel(MultipartFile multipartFile) {
if (multipartFile==null) {
log.error("文件不能為空");
throw new RuntimeException("上傳Excel文件內(nèi)容為空,請重新上傳!");
}
String fileName = multipartFile.getOriginalFilename();
//判斷文件是否是excel文件
assert fileName != null;
if (!fileName.endsWith("xls") && !fileName.endsWith("xlsx")) {
log.error(fileName + "不是Excel文件!");
throw new RuntimeException(fileName + "不是Excel文件!");
}
//保存文件到本地
File dir1 = new File("/roots/uploadFile/xzExcel");
if (!dir1.exists()) {
dir1.mkdirs();
}
//統(tǒng)一日期格式
LocalDateTime current = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formatted = current.format(formatter);
//加上三位隨機數(shù)
Random random = new Random();
int end3 = random.nextInt(999);
File file1 = new File(dir1.getAbsolutePath() + File.separator + formatted + "-" + end3 + "-" + multipartFile.getOriginalFilename());
try {
multipartFile.transferTo(file1);
} catch (IOException e) {
e.printStackTrace();
}
//創(chuàng)建返回對象SalaryExcelOperatVo的實例化對象: result
SalaryExcelOperatVo result = new SalaryExcelOperatVo();
//獲取excel文件sheet1 的內(nèi)容
ArrayList<InSalary> inSalaries1 = readExcel1(file1.getAbsolutePath());
ArrayList<SalaryStaffPerOneListVo> vo1 = new ArrayList<>();
SalaryStaffPerOneListVo oneListVo ;
for(InSalary inSalary1:inSalaries1){
oneListVo = new SalaryStaffPerOneListVo();
BeanUtils.copyProperties(inSalary1,oneListVo);
vo1.add(oneListVo);
}
result.setSheetOne(vo1);
//獲取excel文件sheet2 的內(nèi)容
ArrayList<InSalary> inSalaries2 = readExcel2(file1.getAbsolutePath());
ArrayList<SalaryStaffPerTwoListVo> vo2 = new ArrayList<>();
SalaryStaffPerTwoListVo twoListVo ;
for(InSalary inSalary2:inSalaries2){
twoListVo = new SalaryStaffPerTwoListVo();
BeanUtils.copyProperties(inSalary2,twoListVo);
vo2.add(twoListVo);
}
result.setSheetTwo(vo2);
return result;
}
/**
* 抽離出 【讀取excel文件,包含多個sheet內(nèi)容的方法】
* 1、工作區(qū)間 -> Sheet -> cell;
* 2、每行為一個 inSalary 對象, 把所有對象用 ArrayList保存;
* 從第二行開始讀取,第一行表頭忽略;
*
* @param filePath ;本地保存后的文件地址
* @return ArrayList<InSalary>
*/
private ArrayList<InSalary> readExcel1(String filePath) {
ArrayList<InSalary> inSalary = new ArrayList<>();
// 該監(jiān)聽將excel文件一行一行讀入內(nèi)存(必須有)
SalaryExcelListener listener = new SalaryExcelListener();
ExcelReader excelReader = EasyExcel.read(filePath, InSalary.class,
listener).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
// 這里千萬別忘記關(guān)閉,讀的時候會創(chuàng)建臨時文件,到時磁盤會崩的
excelReader.finish();
// readList 文件中的數(shù)據(jù),不包括表頭
inSalary .addAll(listener.getList());
return inSalary;
}
private ArrayList<InSalary> readExcel2(String filePath) {
ArrayList<InSalary> inSalary = new ArrayList<>();
// 該監(jiān)聽將excel文件一行一行讀入內(nèi)存(必須有)
SalaryExcelListener listener = new SalaryExcelListener();
ExcelReader excelReader = EasyExcel.read(filePath, InSalary.class,
listener).build();
ReadSheet readSheet = EasyExcel.readSheet(1).build();
excelReader.read(readSheet);
// 這里千萬別忘記關(guān)閉,讀的時候會創(chuàng)建臨時文件,到時磁盤會崩的
excelReader.finish();
// readList 文件中的數(shù)據(jù),不包括表頭
inSalary .addAll(listener.getList());
return inSalary;
}
二、多文件的上傳功能?
controller層 (接受前端傳入的文件參數(shù),為多個文件)
@PostMapping(value = "/uploadExcels")
public RespondDto upLoadFiles(@NonNull @RequestParam("multipartFiles") MultipartFile[] multipartFiles,
@NonNull @RequestParam("types") String[] types){
return fileService.upLoadFiles(multipartFiles,types);
}
????????這里需要說明一點,controller層的實現(xiàn)方式有很多中,沒有說當要傳入多個文件的時候一定要去使用數(shù)組的形式進行 ,在這樣寫之前我也嘗試了其他的兩種寫法:
@PostMapping("/upload")
public String upload(@RequestParam("files") List<MultipartFile> files, HttpServletRequest request) {
for (MultipartFile file : files) {
String type1 = request.getParameter("type1");
String type2 = request.getParameter("type2");
// 根據(jù)type1和type2的值,對上傳的文件進行特定處理
// ...
}
return "upload success";
}
@PostMapping(value = "/uploadExcels")
public RespondDto upLoadFiles(@NonNull @RequestParam("multipartFiles") List<MultipartFile> multipartFiles,
@NonNull @RequestParam("types") List<MultipartFile> types){
return fileService.upLoadFiles(multipartFiles,types);
}
?不過實際中具體怎么寫,還是看跟前端的溝通以及最終的接口文檔來進行操作。
service層 (這里實現(xiàn)了將多個Excel一次性上傳后,后端將這些Excel文件進行接收并保存到本地,并且將每個文件的名字以文件類型的名字進行重命名)
@Override
public RespondDto upLoadFiles(MultipartFile[] files, String[] types) {
long MAX_SIZE = 1024 * 1024 * 10;// 文件大小限制為10MB
LocalDateTime current = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = current.format(formatter);
//加上三位隨機數(shù)
Random random = new Random();
int randomNum = random.nextInt(999);
File dir = new File("/manage/uploadFile/"+formattedDate+randomNum);
if (!dir.exists()) {
dir.mkdirs();
}
for (int i = 0; i < files.length; i++) {
MultipartFile file = files[i];
String type = types[i];
// 判斷文件或類型不能為空
if (file.isEmpty() || StringUtils.isEmpty(type)) {
return new RespondDto<>("文件或類型不能為空,請重新上傳!");
}
// 判斷文件大小是否合適
if (file.getSize() > MAX_SIZE) {
return new RespondDto<>("文件過大,上傳失敗!");
}
String originalFileName = file.getOriginalFilename();
// 獲取文件名和擴展名
String fileExt = originalFileName.substring(originalFileName.lastIndexOf("."));
String fileName = type ;
File fileToSave = new File(dir.getAbsolutePath() + File.separator + fileName +fileExt);
try {
//文件寫入 transferTo
file.transferTo(fileToSave);
String fileUrl = fileToSave.getAbsolutePath();
fileUrls.add(fileUrl);
} catch (IOException e) {
e.printStackTrace();
return new RespondDto<>("文件上傳失敗!");
}
log.info("【上傳文件】"+fileName+" 已保存到本地:{}", originalFileName, fileToSave.getAbsolutePath());
}
return new RespondDto<>(fileUrls);
}
?在postman中進行測試,結(jié)果為:
三、文件導出功能
①、導出帶有圖片的Excel
@RestController
@RequestMapping( "/salary/server/excel")
@Slf4j
public class ExportjxExcelsController {
@Value("#{'${headerTitles}'.split(',')}")
private String[] headerTitles;
@Autowired
StaffMapper staffMapper;
@GetMapping("/jxdownload")
public void export( HttpServletResponse response ,Integer year,Integer month) throws IOException {
//設置響應
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 設置要導出的文件名, 這里URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關(guān)系
String fileName= URLEncoder.encode("XXXX數(shù)據(jù)","UTF-8").replaceAll("\\+","%20");
response.setHeader("Content-disposition","attachment;filename*=utf-8''"+fileName+".xlsx");
// 從數(shù)據(jù)庫ensure表中讀取數(shù)據(jù)
List<Salary> userList = staffMapper.getAllStaff(year, month);
log.info("數(shù)據(jù)為:\n{}", userList);
List<Map<String, String>> salaryList = new ArrayList<>();
for (Salary salary : userList) {
Map<String, String> salaryMap = new LinkedHashMap<>();
salaryMap.put("userName", salary.getUserName());
salaryMap.put("firstDepart", salary.getFirstDepart());
salaryMap.put("secondDepart", salary.getSecondDepart());
salaryMap.put("post", salary.getPost());;
salaryMap.put("careDeduct", salary.getCareDeduct());
salaryMap.put("personalTax", salary.getPersonalTax());
salaryMap.put("actualPay", salary.getActualPay());
salaryMap.put("socialUnitpart", salary.getSocialUnitpart());
salaryMap.put("amonthlySalary", salary.getAmonthlySalary());
salaryMap.put("achieveBonus", salary.getAchieveBonus());
salaryMap.put("status", Integer.valueOf(103).equals(salary.getStatus()) ? "已確認" : "未確認");
salaryMap.put("evidence", salary.getEvidence());
salaryList.add(salaryMap);
}
//取出map鍵值對中的value值
List<String> valueList = new ArrayList<>();
for (Map<String, String> salaryMap : salaryList) {
Set<Map.Entry<String, String>> entrySet = salaryMap.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
valueList.add(entry.getValue());
}
}
// 保存文件到本地
File dir = new File("/roots/uploadFile/exportxzExcel");
if (!dir.exists()) {
dir.mkdirs();
}
//加上三位隨機數(shù)
Random random = new Random();
int end3 = random.nextInt(999);
String path = dir.getAbsolutePath() + File.separator + end3 +fileName + ".xlsx";
log.info("path文件路徑為:{}",path);
SSExcel07Workbook ssExcel07Workbook = (new SSExcel07Workbook()).openOrCreate(path);
SSExcel07Sheet sheet = ssExcel07Workbook.createSheet("sheet1");
// 創(chuàng)建Excel表格頭部(有43個字符串)
//將表頭數(shù)據(jù)寫入到單元格中
int col = 0;
for (String title : headerTitles) {
SSExcel07Cell cell = sheet.getCellOrCreate(0, col);
cell.setCellValue(title);
col++;
}
// 將數(shù)據(jù)寫入數(shù)據(jù)到Excel文件中
// 循環(huán)遍歷 salaryList 中的值,寫入到 Excel 文件中
int row = 1;
int col1 = 0;
for (String strval : valueList) {
// 判斷當前列是否超過了最大列數(shù),如果超過了,則重置列數(shù),同時行數(shù)加1
if (col1 >= headerTitles.length) {
col1 = 0;
row++;
}
if(col1==42){
//在最后一列插入圖片
SSExcel07Cell imgCell = sheet.getCellOrCreate(row, 42);
imgCell.insertImg(strval, 0, 0);
}else {
// 獲取當前單元格
SSExcel07Cell cell = sheet.getCellOrCreate(row, col1);
// 將當前單元格的值設置為當前的 strval 值
cell.setCellValue(strval);
}
// 將列數(shù)加1
col1++;
}
ssExcel07Workbook.getXSSFWorkBook().write(response.getOutputStream());
log.info("文件導出完成!");
}
}
②、將之前上傳的多個Excel按照一定的映射規(guī)則生成一個Excel文件(映射規(guī)則可以寫在配置文件中,也可以寫到數(shù)據(jù)庫中,主要看映射關(guān)系的改動頻率是多少)
@Override
public RespondDto writeExcel(String[] fileUrls) throws IOException {
long startTime=System.currentTimeMillis (); //獲取開始時間
String fileUrl = null;
for (String url : fileUrls) {
fileUrl = url;
}
// 讀取配置文件,configFile 是指配置文件的路徑和名稱。
Properties config = new Properties();
ClassPathResource configInputStream = new ClassPathResource("config.properties");
config.load(configInputStream.getInputStream());
// 保存文件到本地
File excelDir = new File("/manage/uploadFile");
if (!excelDir.exists()) {
excelDir.mkdirs();
}
LocalDateTime current = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = current.format(formatter);
//加上三位隨機數(shù)
Random random = new Random();
int randomNum = random.nextInt(999);
// 獲取文件名
String destExcelFileName = formattedDate + "-" + randomNum + "-" ;
FileInputStream destinationFileInputStream = new FileInputStream(excelDir + destExcelFileName);
Workbook destinationWorkbook = new XSSFWorkbook(destinationFileInputStream);
Map<String, Workbook> workbookMap = new HashMap<>();
for (String key : config.stringPropertyNames()) {
String[] destArray = org.springframework.util.StringUtils.tokenizeToStringArray(key, ".");
String[] srcArray = org.springframework.util.StringUtils.tokenizeToStringArray(config.getProperty(key), ".");
System.out.println(srcArray[0]);
if (destArray == null || srcArray == null || srcArray.length != 3 || destArray.length != 2) {
continue;
}
Workbook sourceWorkbook = null;
if (workbookMap.containsKey(srcArray[0])) {
sourceWorkbook = workbookMap.get(srcArray[0]);
} else {
// 讀取源文件
//FileInputStream sourceFileInputStream = new FileInputStream(excelDir + srcArray[0] + ".xlsx");
FileInputStream sourceFileInputStream = new FileInputStream(fileUrl);
sourceWorkbook = new XSSFWorkbook(sourceFileInputStream);
workbookMap.put(srcArray[0], sourceWorkbook);
}
Sheet sourceSheet = sourceWorkbook.getSheet(srcArray[1]);
CellReference sourceCellRef = new CellReference(srcArray[2]);
Row sourceRow = sourceSheet.getRow(sourceCellRef.getRow());
Cell sourceCell = null;
if (sourceRow != null) {
sourceCell = sourceRow.getCell(sourceCellRef.getCol());
}
Sheet destinationSheet = destinationWorkbook.getSheet(destArray[0]);
CellReference destCellRef = new CellReference(destArray[1]);
Row destRow = destinationSheet.getRow(destCellRef.getRow());
if (destRow == null) {
destRow = destinationSheet.createRow(destCellRef.getRow());
}
Cell destCell = destRow.createCell(destCellRef.getCol());
// 執(zhí)行 copy 方法
copyCellValue(sourceCell, destCell);
}
// 保存目標文件
FileOutputStream outputStream = new FileOutputStream(excelDir + destExcelFileName);
destinationWorkbook.write(outputStream);
// 關(guān)閉資源
outputStream.close();
destinationFileInputStream.close();
destinationWorkbook.close();
workbookMap.values().forEach(k -> {
try {
k.close();
} catch (IOException e) {
}
});
long endTime=System.currentTimeMillis (); //獲取結(jié)束時間
System.out.println ( "程序運行時間: " + (endTime-startTime)/1000 + "s" );
return new RespondDto<>("Excel導出成功!");
}
/**
* copyCellValue() 方法用于將源單元格的值復制到目標單元格中。
* @param sourceCell 是源單元格對象
* @param destCell 是目標單元格對象
*/
public static void copyCellValue(Cell sourceCell, Cell destCell) {
// 如果 sourceCell 和 destCell 中任意一個為 null,則不進行操作,方法直接返回。
if (sourceCell == null || destCell == null) {
return;
}
// 首先,將源單元格的單元格樣式克隆到目標單元格上,然后再將源單元格的值賦給目標單元格。
CellStyle sourceStyle = sourceCell.getCellStyle();
CellStyle destinationStyle = destCell.getSheet().getWorkbook().createCellStyle();
destinationStyle.cloneStyleFrom(sourceStyle);
destCell.setCellStyle(destinationStyle);
// 如果源單元格的數(shù)據(jù)類型為字符串類型,則復制字符串值;如果為布爾類型,則復制布爾值;如果為公式類型,則復制公式字符串;如果為數(shù)字類型,則復制數(shù)字值。
if (sourceCell.getCellTypeEnum().equals(CellType.STRING)) {
destCell.setCellValue(sourceCell.getStringCellValue());
} else if (sourceCell.getCellTypeEnum().equals(CellType.BOOLEAN)) {
destCell.setCellValue(sourceCell.getBooleanCellValue());
} else if (sourceCell.getCellTypeEnum().equals(CellType.FORMULA)) {
if (DateUtil.isCellDateFormatted(sourceCell)) {
destCell.setCellValue(sourceCell.getDateCellValue());
} else {
destCell.setCellValue(sourceCell.getNumericCellValue());
}
} else if (sourceCell.getCellTypeEnum().equals(CellType.NUMERIC)) {
if (DateUtil.isCellDateFormatted(sourceCell)) {
destCell.setCellValue(sourceCell.getDateCellValue());
} else {
destCell.setCellValue(sourceCell.getNumericCellValue());
}
// 如果源單元格是空的,則在目標單元格中寫入一個空串("")。
} else if (sourceCell.getCellTypeEnum().equals(CellType.BLANK)) {
destCell.setCellValue("");
}
}
四、注意事項
①、在使用poi的時候一定要注意,選對依賴的版本
不能想用什么依賴的版本就使用什么依賴版本,有些方法,不同的版本之間相差還是比較大的,此處我使用的版本就是3.17的,如果你使用3.9版本的依賴,就不能運行成功。文章來源:http://www.zghlxwxcb.cn/news/detail-516362.html
<!--xlsx(07)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
②、當在上傳多個文件的時候,出現(xiàn)?the request was rejected because its size (10821303) exceeds the configured maximum (10485760)的報錯
????????這是因為springboot默認配置?multipart.max-file-size大小是1M,max-request-size默認大小是10M? 故可以在yml文件中添加:文章來源地址http://www.zghlxwxcb.cn/news/detail-516362.html
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 500MB
到了這里,關(guān)于Java POI (2)—— Excel文件的上傳與導出(實例演示)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!