Spring MVC 实现Excel的导入导出功能(2:Excel的导入优化和Excel的导出)
Excel的导入V2优化版
有些时候文件上传这一步骤由前端来处理,只将上传后的 URL 传输给后端(可以参考上一文中的图片上传功能),也就是导入请求中并不会直接处理 MultipartFile 对象,而是通过文件 URL 将其下载,之后进行文件流处理,具体过程如下:
点击 “ 导入V2 ” 按钮,与 V1 按钮不同,此时出现的不是文件选择框,而是一个上传文件的弹框,点击弹框里的 “ 上传 Excel 文件 ” 按钮才会出现文件选择框,选择正确的文件后点击确定,请求完成后同 V1 版本的导入功能效果一样。
前端实现
HTML页面
<!-- Main content -->
<div class="content">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="grid-btn">
<button class="btn btn-info" onclick="userAdd()">
<i class="fa fa-plus"></i> 新增
</button>
<button class="btn btn-success" onclick="userEdit()">
<i class="fa fa-plus"></i> 编辑
</button>
<button class="btn btn-danger" onclick="deleteUser()">
<i class="fa fa-remove"></i> 删除
</button>
<button class="btn btn-default" id="importV1Button">
<i class="fa fa-upload"></i> 导入ExcelV1
</button>
<button class="btn btn-default" onclick="importV2()"><i
class="fa fa-upload"></i> 导入ExcelV2
</button>
<button class="btn btn-primary" onclick="window.location.href='/users/export'">
<i class="fa fa-download"></i> 导出
</button>
</div>
<table id="jqGrid" class="table table-bordered">
</table>
<div id="jqGridPager"></div>
</div>
</div>
</div>
</div>
</div>
</div>
HTML模态框(这里用的是Custombox)
<!--导入Excel模态框-->
<div class="content">
<div class="modal" id="importV2Modal" tabindex="-1" role="dialog" aria-labelledby="importV2ModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title" id="importV2ModalLabel">用户导入</h6>
</div> <div class="modal-body">
<div class="form-group">
<input type="hidden" id="fileUrl" value="">
<div class="col-sm-10">
<a class="btn btn-default" id="uploadExcelV2">
<i class="fa fa-file">
上传Excel文件
</i>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="cancelImportV2">取消</button>
<button type="button" class="btn btn-primary" id="importV2Button">确认</button>
</div>
</div>
</div>
</div>
</div>
导入的JS
// 用户导入V2
function importV2() {
var modal = new Custombox.modal({
content: {
effect: 'fadein',
target: '#importV2Modal'
}
});
modal.open();
}
new AjaxUpload('#uploadExcelV2', {
action: 'upload/file',
name: 'file',
autoSubmit: 'true',
responseType: 'json',
onSubmit: function (file, extension) {
if (!(extension && /^(xlsx)$/.test(extension.toLowerCase()))) {
alert('只支持xlsx格式的文件!', {
icon: "error",
});
return false;
}
},
onComplete: function (file, r) {
if (r.resultCode == 200) {
$("#fileUrl").val(r.data);
$("#uploadExcelV2").attr('class','btn-info');
alert("上传Excel成功,请点击确认添加数据");
return false;
} else {
alert(r.message);
}
}
});
});
这里还是通过上传获取文件的url再通过流处理转换成File类型并把对应的Excel下载到服务器,其他的和V1版本是一样的
当点击确定导入的时候,如果之前上传没错,再访问一次后台
$("#importV2Button").click(function () {
var fileUrl = $("#fileUrl").val();
$.ajax({
type: 'post',
dataType: 'json',
url: 'users/importV2?fileUrl=' + fileUrl,
contentType:'application/json',
success:function (result) {
if (result.resultCode==200){
closeModal();
reload();
alert("成功导入"+result.data+"条记录!");
}else {
closeModal();
alert(result.message);
};
},
error:function () {
reset();
alert("操作失败!");
}
});
});
后端逻辑
控制层
package com.ssm.demo.controller; import com.ssm.demo.common.Result;
import com.ssm.demo.common.ResultGenerator;
import com.ssm.demo.controller.enums.UploadFileTypeEnum;
import com.ssm.demo.utils.FileUtil;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random; import static com.ssm.demo.common.Constants.FILE_PRE_URL; /**
* Created by 13 on 2017/7/17.
*/
@Controller
@RequestMapping("/upload")
public class UploadFileController { /**
* 通用 文件上传接口(可以上传图片、视频、excel等文件,具体格式可在UploadFileTypeEnum中进行配置)
*
* @return
*/
@RequestMapping(value = "/file", method = RequestMethod.POST)
@ResponseBody
public Result uploadFile(HttpServletRequest request, @RequestParam("file") MultipartFile file) {
ServletContext sc = request.getSession().getServletContext();
String type = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1, file.getOriginalFilename().length());
String fileName = null;
UploadFileTypeEnum uploadFileTypeEnum = UploadFileTypeEnum.getFileEnumByType(type);
if (uploadFileTypeEnum == UploadFileTypeEnum.ERROR_TYPE) {
//格式错误则不允许上传,直接返回错误提示
return ResultGenerator.genFailResult("请检查文件格式!");
} else {
//生成文件名称通用方法
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
Random r = new Random();
StringBuilder tempName = new StringBuilder();
tempName.append(sdf.format(new Date())).append(r.nextInt(100)).append(".").append(type);
fileName = tempName.toString();
}
try {
String dir = sc.getRealPath("/upload");
FileUtils.writeByteArrayToFile(new File(dir, fileName), file.getBytes());
} catch (IOException e) {
//文件上传异常
return ResultGenerator.genFailResult("文件上传失败!");
}
Result result = ResultGenerator.genSuccessResult();
//返回文件的全路径
StringBuilder fileUrl = new StringBuilder();
fileUrl.append(FILE_PRE_URL).append("/upload/").append(fileName);
result.setData(fileUrl.toString());
return result;
} /**
* @param chunks 当前所传文件的分片总数
* @param chunk 当前所传文件的当前分片数
* @return
* @Description: 大文件上传前分片检查
* @author: 13
*/
@ResponseBody
@RequestMapping(value = "/checkChunk")
public Result checkChunk(HttpServletRequest request, String guid, Integer chunks, Integer chunk, String fileName) {
try {
String uploadDir = FileUtil.getRealPath(request);
String ext = fileName.substring(fileName.lastIndexOf("."));
// 判断文件是否分块
if (chunks != null && chunk != null) {
//文件路径
StringBuilder tempFileName = new StringBuilder();
tempFileName.append(uploadDir).append(File.separator).append("temp").append(File.separator).append(guid).append(File.separator).append(chunk).append(ext);
File tempFile = new File(tempFileName.toString());
//是否已存在分片,如果已存在分片则返回SUCCESS结果
if (tempFile.exists()) {
return ResultGenerator.genSuccessResult("分片已经存在!跳过此分片!");
}
}
} catch (Exception ex) {
ex.printStackTrace();
return ResultGenerator.genFailResult("error");
}
return ResultGenerator.genNullResult("不存在分片");
} /**
* @param chunks 当前所传文件的分片总数
* @param chunk 当前所传文件的当前分片数
* @return
* @Description: 大文件分片上传
* @author: 13
*/
@ResponseBody
@RequestMapping(value = "/files")
public Result upload(HttpServletRequest request, String guid, Integer chunks, Integer chunk, String name, MultipartFile file) {
String filePath = null;
//上传存储路径
String uploadDir = FileUtil.getRealPath(request);
//后缀名
String ext = name.substring(name.lastIndexOf("."));
StringBuilder tempFileName = new StringBuilder();
//等价于 uploadDir + "\\temp\\" + guid + "\\" + chunk + ext
tempFileName.append(uploadDir).append(File.separator).append("temp").append(File.separator).append(guid).append(File.separator).append(chunk).append(ext);
File tempFile = new File(tempFileName.toString());
// 判断文件是否分块
if (chunks != null && chunk != null) {
//根据guid 创建一个临时的文件夹
if (!tempFile.exists()) {
tempFile.mkdirs();
}
try {
//保存每一个分片
file.transferTo(tempFile);
} catch (Exception e) {
e.printStackTrace();
}
//如果当前是最后一个分片,则合并所有文件
if (chunk == (chunks - 1)) {
StringBuilder tempFileFolder = new StringBuilder();
//等价于 uploadDir + "\\temp\\" + guid + File.separator
tempFileFolder.append(uploadDir).append(File.separator).append("temp").append(File.separator).append(guid).append(File.separator);
String newFileName = FileUtil.mergeFile(chunks, ext, tempFileFolder.toString(), request);
filePath = "upload/chunked/" + newFileName;
}
} else {
//不用分片的文件存储到files文件夹中
StringBuilder destPath = new StringBuilder();
destPath.append(uploadDir).append(File.separator).append("files").append(File.separator);
String newName = System.currentTimeMillis() + ext;// 文件新名称
try {
FileUtil.saveFile(destPath.toString(), newName, file);
} catch (IOException e) {
e.printStackTrace();
}
filePath = "upload/files/" + newName;
}
Result result = ResultGenerator.genSuccessResult();
result.setData(filePath);
return result;
}
}
FileUtil工具类
package com.ssm.demo.utils; import org.apache.commons.io.FileUtils;
import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.UUID; public class FileUtil {
/**
* 转换MultipartFile对象为java.io.File类型
*
* @param multipartFile
* @return
*/
public static File convertMultipartFileToFile(MultipartFile multipartFile) {
File result = null;
try {
/**
* UUID.randomUUID().toString()是javaJDK提供的一个自动生成主键的方法。
* UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,
* 它保证对在同一时空中的所有机器都是唯一的,是由一个十六位的数字组成,表现出来的形式。
* 由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,
* 过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,
* 全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得),
* UUID的唯一缺陷在于生成的结果串会比较长。
*
*
* File.createTempFile和File.createNewFile()的区别:
* 后者只是创建文件,而前者可以给文件名加前缀和后缀
*/
//这里对生成的文件名加了UUID随机生成的前缀,后缀是null
result = File.createTempFile(UUID.randomUUID().toString(), null);
multipartFile.transferTo(result);
result.deleteOnExit();
} catch (Exception e) {
e.printStackTrace();
}
return result;
} /**
* 根据url获取文件对象
*
* @param fileUrl
* @return
*/
public static File downloadFile(String fileUrl) {
File result = null;
try {
result = File.createTempFile(UUID.randomUUID().toString(), null);
URL url = new URL(fileUrl);
URLConnection connection = url.openConnection();
connection.setConnectTimeout(3000);
BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(result));
byte[] car = new byte[1024];
int l = 0;
while ((l = bis.read(car)) != -1) {
bos.write(car, 0, l);
}
bis.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
} /**
* @param request
* @return
*/
public static String getRealPath(HttpServletRequest request) {
ServletContext sc = request.getSession().getServletContext();
String uploadDir = sc.getRealPath("/upload");
return uploadDir;
} public static boolean saveFile(String savePath, String fileFullName, MultipartFile file) throws IOException {
File uploadFile = new File(savePath + fileFullName);
FileUtils.writeByteArrayToFile(new File(savePath, fileFullName), file.getBytes());
return uploadFile.exists();
} public static String mergeFile(int chunksNumber, String ext, String uploadFolderPath,
HttpServletRequest request) {
//合并分片流
String mergePath = uploadFolderPath;
String destPath = getRealPath(request);// 文件路径
String newName = System.currentTimeMillis() + ext;// 文件新名称
SequenceInputStream s;
InputStream s1;
try {
s1 = new FileInputStream(mergePath + 0 + ext);
String tempFilePath;
InputStream s2 = new FileInputStream(mergePath + 1 + ext);
s = new SequenceInputStream(s1, s2);
for (int i = 2; i < chunksNumber; i++) {
tempFilePath = mergePath + i + ext;
InputStream s3 = new FileInputStream(tempFilePath);
s = new SequenceInputStream(s, s3);
}
//分片文件存储到/upload/chunked目录下
StringBuilder filePath = new StringBuilder();
filePath.append(destPath).append(File.separator).append("chunked").append(File.separator);
saveStreamToFile(s, filePath.toString(), newName);
// 删除保存分块文件的文件夹
deleteFolder(mergePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return newName;
} private static boolean deleteFolder(String mergePath) {
File dir = new File(mergePath);
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
try {
file.delete();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return dir.delete();
} private static void saveStreamToFile(SequenceInputStream inputStream, String filePath, String newName)
throws Exception {
File fileDirectory = new File(filePath);
synchronized (fileDirectory) {
if (!fileDirectory.exists()) {
if (!fileDirectory.mkdir()) {
throw new Exception("文件夹创建失败,路径为:" + fileDirectory);
}
}
if (!fileDirectory.exists()) {
if (!fileDirectory.mkdir()) {
throw new Exception("文件夹创建失败,路径为:" + fileDirectory);
}
}
}
OutputStream outputStream = new FileOutputStream(filePath + newName);
byte[] buffer = new byte[1024];
int len = 0;
try {
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
outputStream.flush();
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
outputStream.close();
inputStream.close();
}
}
}
其他的和V1版本是一样的
mapper
<update id="deleteUser">
update tb_admin_user set is_deleted = 1
where id in
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
效果展示
Excel的导出实现
前端实现
<button class="btn btn-primary"
onclick="window.location.href='/users/export'">
<i class="fa fa-download"></i> 导出
</button>
点击的时候会直接访问到Controller层来获取下载
后端逻辑
/**
* 导出功能
*/
@RequestMapping(value = "/export", method = RequestMethod.GET)
public void exportUsers(HttpServletRequest request, HttpServletResponse response) {
List<AdminUser> userList = adminUserService.getUsersForExport();
//单元格表头
String[] excelHeader = {"用户id", "用户名", "账号状态", "添加时间"};
//字段名称
String[] fileds = {"userId", "userName", "status", "createTime"};
//单元格宽度内容格式
int[] formats = {4, 2, 1, 1};
//单元格宽度
int[] widths = {256 * 14, 512 * 14, 256 * 14, 512 * 14};
try {
List<Map<String, Object>> excelData = new ArrayList<Map<String, Object>>();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (CollectionUtils.isNotEmpty(userList)) {
for (AdminUser user : userList) {
Map<String, Object> map = new HashMap<>();
map.put("userId", user.getId());
map.put("userName", user.getUserName());
map.put("status", user.getIsDeleted() == 0 ? "正常账号" : "废弃账号");
map.put("createTime", formatter.format(user.getCreateTime()));
excelData.add(map);
}
}
String excelName = "用户数据_" + System.currentTimeMillis();
PoiUtil.exportFile(excelName, excelHeader, fileds, formats, widths, excelData, request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
注:这里的一些工具类在上一篇写过了
Spring MVC 实现Excel的导入导出功能(2:Excel的导入优化和Excel的导出)的更多相关文章
- spring mvc + ehcache 利用注解实现缓存功能
我的spring是3.1的,因为项目需求,需要在查询时候加上缓存,小白一个,完全没有用过缓存(ehcache),摸索了一天终于会了一点通过注解来使用ehcache进行缓存,立刻给记录下来. 首先 我的 ...
- spring mvc配置完后实现下载功能
实现是前台: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEn ...
- 通用后台管理系统(ExtJS 4.2 + Spring MVC 3.2 + Hibernate)
通用后台管理系统(ExtJS 4.2 +Spring MVC 3.2 + Hibernate) 开发语言JAVA 成品成品 前端技术extjs 数据库mysql,sql server,oracle 系 ...
- Spring MVC 基础注解之@RequestMapping、@Controller、(二)
我现在学的是spring4.2 今天主要学习了Spring MVC注解 引入注解可以减少我们的代码量,优化我们的代码. @Controller:用于标识是处理器类: @RequestMapping:请 ...
- [读后感]spring Mvc 教程框架实例以及系统演示下载
[读后感]spring Mvc 教程框架实例以及系统演示下载 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致&qu ...
- GrapeCity Documents for Excel 与 Apache POI 功能对比
GrapeCity Documents for Excel 与 Apache POI 功能对比 GrapeCity Documents for Excel 是什么? GrapeCity Documen ...
- Spring MVC——项目的开发流程
创建项目(IDEA下) 打开IDEA,我们开始创建一个简单的Spring MVC项目,流程如下: 这里要注意一下,我们是基于Maven开发项目,当然是要配置Maven环境的,如果大家之前从来没有配置过 ...
- 高性能的关键:Spring MVC的异步模式
我承认有些标题党了,不过话说这样其实也没错,关于“异步”处理的文章已经不少,代码例子也能找到很多,但我还是打算发表这篇我写了好长一段时间,却一直没发表的文章,以一个更简单的视角,把异步模式讲清楚. 什 ...
- Spring MVC 3.0.5+Spring 3.0.5+MyBatis3.0.4全注解实例详解(三)
前两章我为大家详细介绍了如何搭建Maven环境.Spring MVC的流程结构.Spring MVC与Struts2的区别以及示例中的一些配置文件的分析.在这一章,我就对示例的层次结构进行说明,以及M ...
- Spring MVC的异步模式
高性能的关键:Spring MVC的异步模式 我承认有些标题党了,不过话说这样其实也没错,关于“异步”处理的文章已经不少,代码例子也能找到很多,但我还是打算发表这篇我写了好长一段时间,却一直没发表 ...
随机推荐
- 六、linux目录结构知识
1.显示行号: cat -n 2.set nu 3.tail -f a.txt 查看文件的尾部变化 4.w 当前的登陆用户 5.yum包管理工具底层调用的还是 rpm -ivh 包名 ...
- 【ocp-12c】最新Oracle OCP-071考试题库(43题)
43.(9-2)choose three Which three tasks can be performed by DDL statements? A) preventing data retrie ...
- LOJ#6046. 「雅礼集训 2017 Day8」爷(分块)
题面 传送门 题解 转化为\(dfs\)序之后就变成一个区间加,区间查询\(k\)小值的问题了,这显然只能分块了 然而我们分块之后需要在块内排序,然后二分\(k\)小值并在块内二分小于它的元素--一个 ...
- django 视图 使用orm values_list()方法获取 指定的 多个字段的数据
from .models import UserInfodata_set = UserInfo.objects.all().values_list("user_name", &qu ...
- 【微信小程序】——wxss引用外部CSS文件及iconfont
小程序引入外部文件的方式是:@import "*/*.wxss"; 因为业务需要,正在开发的小程序中需要使用iconfont,很容易想到了H5的引入方式: ```` @font-f ...
- IDEA 引入外部jar包 pom 配置,防止打包失败
1. <!--添加外部依赖--> <dependency> <groupId>Ice</groupId> ...
- K:java中的安全模型(沙箱机制)
本博文整合自:Java安全--理解Java沙箱.Java 安全模型介绍.Java的沙箱机制原理入门 相关介绍: 我们都知道,程序员编写一个Java程序,默认的情况下可以访问该机器的任意资源,比如读取 ...
- Common xaml controls(补交作业)
Common xaml controls 常见的xaml控件: 先上一段代码,把他们基本都实现出来: <Grid Name="MyGrid"> <Button N ...
- zookeeper 高可用集群搭建
前言 记录Zookeeper集群搭建的过程! 什么是 Zookeeper ? ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hado ...
- js的window对象
js的window对象 1.子窗口方法 function testOpen(){ window.open('son.html','newwindow','height=400, width=600, ...