引入依赖
<!--导入excel依赖包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
<!--用于将MultipartFile文件转化为File文件-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
导出Excel文件
springboot
实体类
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER)//表头文字居中
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER)//内容文字居中
public class Topic {
// @ExcelIgnore //忽略字段
// @ExcelProperty(String) //设置字段名
// @HeadRowHeight(int):设置表头高度(与 @ContentRowHeight 相反)标记在类上
// @ContentRowHeight(int):设置 row 高度,不包含表头标记在 类上
// @ColumnWidth(int):设置列宽标记在属性上
//题目id
@ExcelIgnore //忽略该字段
private Long id;
//题库id
@ExcelIgnore
private Long fileId;
//题目
@ExcelProperty("题目") //设置字段名
@ColumnWidth(20)
private String question;
//题目选项,填空题、问答题,为空
@ExcelProperty("选项")
@ColumnWidth(20)
private String options;
//题目的正确答案
@ExcelProperty("答案")
@ColumnWidth(20)
private String answer;
//题目解析
@ExcelProperty("解析")
@ColumnWidth(20)
private String explains;
//题目类型
@ExcelProperty("题目类型")
@ColumnWidth(15)
private String type;
//题目的最后修改时间
@ExcelProperty("更新时间")
@ColumnWidth(20)
private LocalDateTime updateTime;
}
在Controller层中,接收题库文件id,用于导出该题库的所有题目
@PostMapping ("/export/{fileId}")
public void exportExcel(HttpServletResponse response,@PathVariable("fileId") Long fileId) throws Exception {
importExportService.exportExcel(response,fileId);
}
在Service中实现文件导出
@Autowired
TopicMapper topicMapper;
@Override
public void exportExcel(HttpServletResponse response,Long fileId) throws IOException {
QueryWrapper<Topic> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("file_id",fileId);
List<Topic> topics = topicMapper.selectList(queryWrapper);
// 设置下载类型
response.setContentType("application/octet-stream; charset=utf-8");
response.setCharacterEncoding("utf-8");
//设置要导出的excel文件名,返回到响应头上的文件名,真实下载的文件名,需要在前端设置
String fileName = URLEncoder.encode("题库", "UTF-8").replaceAll("\\+", "%20");//将编码后的空格转换回正常的空格字符。
response.setHeader("Content-disposition","filename="+fileName+".xlsx");
EasyExcel.write(response.getOutputStream())
.head(Topic.class)
.excelType(ExcelTypeEnum.XLSX)
.sheet("数据")
.doWrite(topics);
}
Vue
当用户点击“导出”,调用exportExcelFile()方法,发起axios请求,这边对axios进行了封装。封装链接如下基础Axios封装与使用(基本流程步骤)_axios封装使用_没有手的前端的博客-CSDN博客
//导出excel文件
async exportExcelFile(fileId,fileName){
try{
const res=await bankFile.exportExcel(fileId)
const blob = res; // 这里的 res 就是包含 Excel 文件的 Blob 对象
// 创建一个下载链接
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fileName+'.xlsx'; // 设置文件名
a.style.display = 'none'; // 隐藏下载链接
// 将下载链接添加到页面,并模拟点击触发下载
document.body.appendChild(a);
a.click();
// 释放 Blob URL 和移除下载链接
URL.revokeObjectURL(a.href);
document.body.removeChild(a);
console.log(res);
}catch(err){
this.$message.error('导出失败')
}
}
bankFile.exportExcel(fileId)如下
exportExcel(fileId){
return axios.post(`/user/${fileId}`,null,{
responseType:'blob' //这将响应类型设置为 'blob',表示 API 的响应预期是一个二进制对象
})
}
需要注意的时,我这边的res,就是res.data
res => {
//在响应拦截器中,若响应成功,将res.data赋值给res
return res.status === 200 ? Promise.resolve(res.data) : Promise.reject(res)
},
Excel文件导入数据库
springboot
在Controller层中,接收所要导入的题库id
@Autowired
ImportExportService importExportService;
@ApiOperation("导入excel")
@PostMapping("/import/{fileId}")
public Result importExcel(@RequestParam("file") MultipartFile file,@PathVariable("fileId") Long fileId) throws Exception {
return importExportService.importExcel(file,fileId);
}
在Service中需要将MultipartFile文件类型转化为File类型才能读取,注意:这边需要将topicMapper对象传递给监听器,在监听器中将题目插入到数据库
@Override
public Result importExcel(MultipartFile multipartFile,@PathVariable("fileId") Long fileId) {
// 将 MultipartFile 转换为 File
File file = null;
InputStream inputStream=null;
try {
file = convertMultipartFileToFile(multipartFile);
inputStream = new FileInputStream(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
//创建一个读取excel的监听器,在监听器中将数据插入到数据库
ImportExcelListener importExcelListener = new ImportExcelListener(fileId,topicMapper);
/**
* 流、读取的类型对象、监听器对象
* 读取工作簿下的第几个工作表、标题行的行数
*/
try {
EasyExcel.read(inputStream, Topic.class,importExcelListener).sheet(0).headRowNumber(1).doRead();
}catch (Exception e){
log.error("导入发生异常",e);
throw new BaseException(e.getMessage());
}finally {
// 操作完成后不管成功与否需要删除在根目录下生成的文件
File f = new File(file.toURI());
if (f.delete()){
System.out.println("删除成功");
}else {
System.out.println("删除失败");
}
}
// 操作完上的文件 需要删除在根目录下生成的文件
return Result.success("数据导入成功");
}
MultipartFile转化为File的方法如下
private File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
File file = new File(multipartFile.getOriginalFilename());
try (FileOutputStream outputStream = new FileOutputStream(file)) {
outputStream.write(multipartFile.getBytes());
}
return file;
}
监听器代码如下:通过有参构造,接收参数。每读取一行会促发一次Invoke(),对每一行数据进行了简单的格式校验,若格式全部正确,在doAfterAllAnalysed()中插入,否在将错误信息抛给前端,前端展示给用户
public class ImportExcelListener implements ReadListener<Topic> {
private Long fileId;
private TopicMapper topicMapper; // 添加成员变量
public ImportExcelListener(Long fileId, TopicMapper topicMapper) {
this.fileId = fileId;
this.topicMapper = topicMapper; // 注入 topicMapper
}
LinkedList<Topic> topics=new LinkedList<>();
//每读取一行数据促发一次
@Override
public void invoke(Topic data, AnalysisContext context) {
if(Objects.isNull(data)){
throw new RuntimeException("数据不能为空错误");
}
if(data.getQuestion().equals("")||data.getAnswer().equals("")||data.getType().equals("")){
throw new RuntimeException("必须包含题目和答案,并正确设置题目类型");
}
if((data.getType().equals("单选题")||data.getType().equals("多选题"))){
String[] split = data.getOptions().split(",");
if(split.length<2){
throw new RuntimeException("选择题选项不小于2");
}
}
data.setFileId(fileId);
try {
topics.add(data);
}catch (Exception e){
throw new RuntimeException("文件格式错误");
}
}
//全部读取完毕后促发一次
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
try {
for (Topic topic : topics) {
topicMapper.insert(topic);
}
} catch (Exception e) {
throw new RuntimeException("文件格式错误");
}
System.out.println("导入完毕");
}
}
vue
:action="importExcelFile(scope.row.id)"//将id传给mportExcelFile()函数
:show-file-list="false" //关闭文件上传列表
:auto-upload="true" //选择文件后,自动提交
:on-success="handleExcelSuccess" //文件上传后调用handleExcelSuccess函数
accept=".xlsx" //上传的文件类型
:headers="headers" //携带token,若没有token可不用
<el-upload
:action="importExcelFile(scope.row.id)"
:show-file-list="false"
:auto-upload="true"
:on-success="handleExcelSuccess"
accept=".xls,.xlsx"
class="upload-resource"
:headers="headers"
>
导入
</el-upload>
importExcelFile()函数
//导入Excel
importExcelFile(fileId){
return `http://localhost:8080/user/import/${fileId}`
},
handleExcelSuccess()函数
//Excel上传成功后促发
handleExcelSuccess(res){
if(res.code===200){
this.$message.success(res.msg)
}else{
this.$message.error(res.msg)
}
console.log(res);
},
headers需要携带token
return(){
headers:{
token:localStorage.getItem('token')
},
}